1. 概要
CLIENTからのリクエストに応じてJooby(Kotlin)からBlenderを起動、3Dモデルの寸法を編集(縦なんcm、横なんcm)、
更に画像を貼り付けるといった加工を行っています。(画像は格納先のURLを貰う形式)
レスポンスには生成したGLBファイルへのアクセスURLを返却するようにしています。
2. ソースコード&環境の準備
最終的な物はGithubにあげました。
https://github.com/TakuyaShirosaka/JoobyBlender
Jooby
Joobyに関しては私の投稿記事でも何回かテーマしたことがあるのと、
他の方の記事の方が正確だと思うので紹介は省きます。
開発環境としてはDocker/Docker-composeを使用しています。
version: '3'
services:
app:
container_name: ar_app
shm_size: 4096m
build: "./application"
ports:
- '8015:8080'
- '8050:8050'
- '5005:5005'
volumes:
- "./application:/app"
- "./sys/fs/cgroup:/sys/fs/cgroup:ro"
- "./work:/work"
environment:
TZ: 'Asia/Tokyo'
GRADLE_OPTS: '-Dorg.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'
working_dir: /app
cap_add:
- SYS_ADMIN
security_opt:
- seccomp:unconfined
tty: true
networks:
- shared_network
networks:
shared_network:
external: true
shared_networkというのは後述するlocalstackの為の共有ネットワークです。
コンテナ内で
./gradlew joobyrun
でアプリケーションが起動します。
Blender
Dockerfile内でバージョン等を指定してダウンロード
pythonMacroのフォルダは実行時のパスさえ通せればどこでも良いと思います。
#blender
ENV BLENDER_MAJOR 2.83
ENV BLENDER_VERSION 2.83.10
WORKDIR /
RUN wget https://download.blender.org/release/Blender${BLENDER_MAJOR}/blender-${BLENDER_VERSION}-linux64.tar.xz -O blender.tar.xz
RUN mkdir blender && tar xvf blender.tar.xz -C blender --strip-components 1
RUN chmod 600 /blender
ENV PATH $PATH:/blender
RUN rm blender.tar.xz
#python
COPY ./pythonMacro /pythonMacro
RUN chmod -R 777 /pythonMacro
インストール場所はパスさえ通せれば自分の好きな場所で良いと思います。
ひな型に使用する3Dモデルはデフォルトを1m×1mにして、空の画像テクスチャを適用しておきます。
こういったコードでBlenderでひな型の3Dモデルを開く~選択の初期化~寸法の編集~画像テクスチャに画像の適用ができます。
def _blenderProcessing(self):
self.logger.info("BlenderMacro_wall-blenderProcessing")
# .blenderファイルを開く
bpy.ops.wm.open_mainfile(filepath=self.blenderFilePath)
# 選択を外す
for obj in bpy.context.scene.objects:
obj.select_set(False)
# Boxオブジェクトを指定
obj = bpy.data.objects['Box']
obj.select_set(True)
# 寸法の変更
self._change_object_dimensions(obj)
# アクティブなマテリアルの選択、Nodeを有効にする
bpy.context.view_layer.objects.active = obj
mat = bpy.context.view_layer.objects.active.active_material
mat.use_nodes = True
# 3Dオブジェクトに画像テクスチャを付与する
imageNode = mat.node_tree.nodes["画像テクスチャ"]
imageNode.image = bpy.data.images.load(self.imageFilePath)
for node in mat.node_tree.nodes:
self.logger.info(node)
def _change_object_dimensions(self, selectob='Default'):
# 各辺の寸法を取得する
selectdimX = selectob.dimensions[0]
selectdimY = selectob.dimensions[1]
selectdimZ = selectob.dimensions[2]
# リサイズするための倍率を取得する
# X:1 Y:0.01 Z:2
magnificationX = self.width / selectdimX
magnificationZ = self.height / selectdimZ
self.logger.info("self.width:" + str(self.width))
self.logger.info("self.height:" + str(self.height))
# 拡大後の寸法を取得します
changedimX = selectdimX * magnificationX
changedimY = selectdimY
changedimZ = selectdimZ * magnificationZ
changedimensions = (changedimX, changedimY, changedimZ)
self.logger.info("changedimX:" + str(changedimX))
self.logger.info("changedimY:" + str(changedimY))
self.logger.info("changedimZ:" + str(changedimZ))
# 寸法を変更する
selectob.dimensions = changedimensions
def _glbFileExport(self):
self.logger.info("BlenderMacro_wall-glbFileExport-start")
bpy.ops.export_scene.gltf(export_format='GLB', filepath=self.glbFileExportPath)
self.logger.info("BlenderMacro_wall-glbFileExport-end")
localstack
今回はファイルの保存先に使用するAWS S3をlocalstackで代替しています。
joobyBlender/localstack/docker-compose.yml
こちらにもymlがあります。
ネットワークにはアプリケーションと同じくshared_networkを使用するようにしています。
#!/usr/bin/env bash
echo "storage_init START!"
aws --region ap-northeast-1 --endpoint-url=http://localstack:4566 s3 mb s3://ar-bucket
aws --endpoint-url=http://localstack:4566 s3api put-bucket-acl --bucket ar-bucket --acl public-read
echo "storage_init END!"
必要なバケットなどはこのShell内で作ってしまいましょう。
他のコンテナからでもネットワークさえ同じであればコンテナ間で通信できるので開発時にはとても便利です。
3. いざ実行
localstackであれば
aws s3 cp s3://ar-bucket/AAA/BBB/sento.glb ./ --endpoint-url=http://localhost:4566
このようにエンドポイントをlocalstackに向けてやるとファイルが取得できます。
4. 苦労したこと
・もともと業務で行ったことのリバースエンジニアリング的なことから始まりました。
ネットで調べても同じような前例がない(というかこんなことしてるの自分だけでは。。。?)
・Kotlinで書いてる部分は殆どのプログラミング言語で実現できるので、Kotlinのおさらい的な気分で開発していました。
Blenderの起動はpythonからキックするような形になります。
端折っていますが、下記のように外部プロセスで実行するようにしています。
この方法で良いのかはずっと悩んでいます。
fun processRun(pb: ProcessBuilder) {
val process = pb.start()
val ret = process.waitFor()
return run {
logger.info("★Ret:${ret}")
this.dispProcessLog(process)
if (ret != 0) {
throw Exception("${pb.command()} is failed")
}
}
}
/* 外部プロセスでBlenderコマンド、Pythonマクロの実行 */
ProcessBuilder(
BlenderUtility.BLENDER_COMMAND,
"-b",
"-P",
arMaterials.pythonFilePath,
"--",
param.blenderFilePath,
param.imageFilePath,
param.glbFileName,
param.width.toString(),
param.height.toString()
).let {
super.processRun(it)
}
・この仕組みを最初に実装した時はPythonに触れたことがありませんでした。
+Blenderの機能を利用する場合、yumとかでインストールできるPythonではなくBlender内部にあるPythonを利用することになるようで、
Pythonの基本的な知識を学習しながらこのように対応しました。
# coding: UTF-8
# Blenderの組み込みPythonを利用するのでPathがBlender内部に設定される
# そのままだと自作のモジュールが読み込めないので明示的にパスを通す
import time
import sys
import bpy
import traceback
sys.path.append('./')
sys.path.append('/pythonMacro')
sys.path.append('/pythonMacro/Controller')
・今回はGLBファイルの作成までをご紹介しましたが、IOS向けのudszファイルの作成方法については機会があれば投稿します。