Pythonista の objc_util で SceneKit を使うと iOS のみで 3D 描画できるスクリプトを見つけたので紹介します。
元記事
3d-omz.py 注:load_framework(‘SceneKit’) を足さないとエラーになる
https://github.com/tdamdouni/Pythonista/blob/master/scenes/3d-omz.py
3D in Pythonista
https://forum.omz-software.com/topic/1686/3d-in-pythonista
実行イメージ
解説
from objc_util import *
objc_util モジュールが提供する機能を直接呼び出せるようにします。
objc_util モジュールは標準モジュールの ctypes をベースにしているので、この一行で ctypes モジュールが提供する Structure なども使えるようになります。
load_framework('SceneKit')
SceneKitフレームワークを読み込んで、プログラム上で操作できるようにします。
3d-omz.py では記載されていなかったので、昔は呼ぶ必要がなかったのかもしれませんが、iPadOS13 ではこれがないと SceneKit が提供する SCNView クラスなどを ObjCClass で Python で使える形式に変換する際に no Objective-C class named ‘xxxx’ found というエラーで怒られてしまいます。
SCNView, SCNScene, SCNBox, SCNText, SCNNode, SCNLight, SCNAction, UIFont = map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox', 'SCNText', 'SCNNode', 'SCNLight', 'SCNAction', 'UIFont'])
SceneKit が提供するクラス群を obj_util.ObjCClass を使って Python で使える形式に変換しています。
標準関数の map を使うと同じ処理をまとめて記述できるので楽ですよね。
class SCNVector3 (Structure):
_fields_ = [('x', c_float), ('y', c_float), ('z', c_float)]
ctypes.Structure を使って、float型の3つの属性(x,y,z)を持つ構造体を定義します。
ここで定義した構造体は後で Text の BoundingBox を取得する際に使います。
main_view = ui.View()
main_view_objc = ObjCInstance(main_view)
空の ui.View を作成し、その View インスタンスを objc_util.ObjCInstance に渡す事でに Objective-C からアクセスするためのポインタを取得しています。
後で SceneKit の View をこの main_view の子供としてぶら下げる事で、Pythonista の ui.View の中に SceneKit の描画物を表示できるみたいです。
(詳細は勉強不足で理解できていません)
scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(400, 400)), None).autorelease()
scene_view.setAutoresizingMask_(18)
scene_view.setAllowsCameraControl_(True)
SCNView のインスタンスを alloc() メソッドを呼んで作成。
その後 initWithFrame:options: を呼んで、位置(0,0)で大きさ(400,400)に初期化します。
最後にインスタンスに autorelease()を呼ぶ事で、不要になったらメモリを自動で破棄するように設定しています。
SCNView.initWithFrame:options:
https://developer.apple.com/documentation/scenekit/scnview/1524215-initwithframe?language=objc
setAutoresizeMask_ は、親 View のサイズが変わった時にどのような処理をするか設定するメソッドです。
18 という数字は flexibleWidth の 2 と、flexibleHeight の16 の論理和で、これを設定する事で親 View の大きさが変わった時に、幅と高さを追従する形になります。
これは Pythonista の ui モジュールの View クラスの flex 属性と同じだと思うとイメージしやすいと思います。
UIView.AutoresizingMask
https://developer.apple.com/documentation/uikit/uiview/1622559-autoresizingmask
setAllowsCameraControl_を True にすると画面をスワイプしたりしてカメラを回転させたりできるようになります。なんらかのビューアーや、開発の初期の動作確認ではこれを True にしてしまうとが手軽で良いです。
scene = SCNScene.scene()
node_root = scene.rootNode()
SceneKit で管理するカメラやライト、描画物は全て Node で階層付けされています。
SCNScene の rootNode はその階層構造の一番親にあたるものです。
この後、 rootNode に対して色々な node を追加することで、画面に何をどのように表示するかが決定されていきます。