LoginSignup
0

More than 1 year has passed since last update.

posted at

サーバーサイドKotlin(Jooby)でBlenderと連携してみる

1. 概要

こんなことをやってます。というのをざっくりと
image.png

CLIENTからのリクエストに応じてJooby(Kotlin)からBlenderを起動、3Dモデルの寸法を編集(縦なんcm、横なんcm)、
更に画像を貼り付けるといった加工を行っています。(画像は格納先のURLを貰う形式)

レスポンスには生成したGLBファイルへのアクセスURLを返却するようにしています。

2. ソースコード&環境の準備

最終的な物はGithubにあげました。
https://github.com/TakuyaShirosaka/JoobyBlender

Jooby

Joobyに関しては私の投稿記事でも何回かテーマしたことがあるのと、
他の方の記事の方が正確だと思うので紹介は省きます。

開発環境としてはDocker/Docker-composeを使用しています。

joobyBlender/docker-compose.yml
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のフォルダは実行時のパスさえ通せればどこでも良いと思います。

joobyBlender/application/Dockerfile
#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にして、空の画像テクスチャを適用しておきます。
image.png

こういったコードでBlenderでひな型の3Dモデルを開く~選択の初期化~寸法の編集~画像テクスチャに画像の適用ができます。

joobyBlender/application/pythonMacro/BlenderMacro_wall.py
    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を使用するようにしています。

joobyBlender/localstack/build/storage_init/storage_init.sh
#!/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. いざ実行

こんな感じにレスポンスが返ってきます。
image.png

localstackであれば

aws s3 cp s3://ar-bucket/AAA/BBB/sento.glb  ./ --endpoint-url=http://localhost:4566

このようにエンドポイントをlocalstackに向けてやるとファイルが取得できます。
image.png

今度は寸法を変えてみます。
image.png

このようにパラメータによって大きさが変わります。
image.png

最後にもう一パターン
image.png

どん
image.png

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の基本的な知識を学習しながらこのように対応しました。

joobyBlender/application/pythonMacro/Controller/BlenderMacro_wall_Controller.py
# 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ファイルの作成方法については機会があれば投稿します。
 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0