この記事は、Pythonista3 Advent Calendar 2022 の19日目の記事です。
一方的な偏った目線で、Pythonista3 を紹介していきます。
ほぼ毎日iPhone(Pythonista3)で、コーディングをしている者です。よろしくお願いします。
以下、私の2022年12月時点の環境です。
--- SYSTEM INFORMATION ---
* Pythonista 3.3 (330025), Default interpreter 3.6.1
* iOS 16.1.1, model iPhone12,1, resolution (portrait) 828.0 x 1792.0 @ 2.0
他の環境(iPad や端末の種類、iOS のバージョン違い)では、意図としない挙動(エラーになる)なる場合もあります。ご了承ください。
ちなみに、model iPhone12,1
は、iPhone11 です。
この記事でわかること
- ARKit をPythonista3 で起動する
- 前面カメラで世界を映す
- AR 世界にオブジェクトを出す
Pythonista3 の拡張現実の世界へようこそ
拡張現実(AR)の開発ができるARKit も、objc_util
から呼び出しが可能です。
AR とは、ARKit とは。の部分は別の解説に譲るとして、この記事ではPythonista3 でAR が使える状態になるまでを紹介します。
ARKit - 日本語ドキュメント - Apple Developer
RealityKit
は呼び出せないかも
iOS 関係でAR を調べると、ARKit 以外にRealityKit
もありますが、Pythonista3 でRealityKit は(私の観測上だと)呼び出せません。
(私が察する理由ですが)Swift でしか開発ができないので、objc_util
で呼び出せないのだと思われます。
自分が実装してみたい内容の背景が、何で構成されているのかの確認は必要ですね。
RealityKitの概要 - 拡張現実 - Apple Developer
ARKit とSceneKit(または、SpriteKit) の連携
前回までのSceneKit の技術をたくさん使います。
ARKit で取得した現実情報とSceneKit のSCNScene
を結びつけ画面上に登場させるイメージです。
「これはARKit での処理、こっちはSceneKit の処理」と課題の分離ができると、確認用のミニマムな実装も行いやすいと思います。
ARSCNView → SCNView からAR のView に変更
|
SCNScene
|
rootNode
↑ ↑ ↑ ↑ addChildNode(Node)
さまざまなNode たち
AR の世界へダイブ
ARKit のミニマムな内容と、SceneKit 生成したBox を呼び出します。
リアカメラから現実世界を取得し、SceneKit のBox はその場に止まっている状態です。
ちなみにload_framework('SceneKit')
の呼び出しは不要です。
load_framework('ARKit')
とARKit をロードすることで、SceneKit も呼び出されます。
from objc_util import load_framework, ObjCClass
from objc_util import UIColor
import ui
import pdbg
load_framework('ARKit')
ARSCNView = ObjCClass('ARSCNView')
ARWorldTrackingConfiguration = ObjCClass('ARWorldTrackingConfiguration')
SCNScene = ObjCClass('SCNScene')
SCNNode = ObjCClass('SCNNode')
SCNAction = ObjCClass('SCNAction')
SCNBox = ObjCClass('SCNBox')
class GameScene:
def __init__(self):
self.scene: SCNScene
self.setUpScene()
def setUpScene(self):
scene = SCNScene.scene()
scene_rootNode_addChildNode_ = scene.rootNode().addChildNode_
box = SCNBox.boxWithWidth_height_length_chamferRadius_(0.5, 0.5, 0.5, 0.08)
box.firstMaterial().diffuse().contents = UIColor.blueColor()
geometryNode = SCNNode.nodeWithGeometry_(box)
geometryNode.position = (0, -1.0, -1.0)
geometryNode.runAction_(
SCNAction.repeatActionForever_(
SCNAction.rotateByX_y_z_duration_(0.0, 0.2, 0.1, 0.3)))
scene_rootNode_addChildNode_(geometryNode)
self.scene = scene
class ViewController:
def __init__(self):
self.sceneView: ARSCNView
self.scene: GameScene
self.viewDidLoad()
self.viewWillAppear()
def viewDidLoad(self):
scene = GameScene()
_frame = ((0, 0), (100, 100))
sceneView = ARSCNView.alloc().initWithFrame_(_frame)
sceneView.autoresizingMask = (1 << 1) | (1 << 4)
sceneView.autoenablesDefaultLighting = True
sceneView.showsStatistics = True
''' debugOptions
OptionNone = 0
ShowPhysicsShapes = (1 << 0)
ShowBoundingBoxes = (1 << 1)
ShowLightInfluences = (1 << 2)
ShowLightExtents = (1 << 3)
ShowPhysicsFields = (1 << 4)
ShowWireframe = (1 << 5)
RenderAsWireframe = (1 << 6)
ShowSkeletons = (1 << 7)
ShowCreases = (1 << 8)
ShowConstraints = (1 << 9)
ShowCameras = (1 << 10)
ARSCNDebugOptionShowFeaturePoints = (1 << 30)
ARSCNDebugOptionShowWorldOrigin = (1 << 32)
'''
_debugOptions = (1 << 1) | (1 << 30) | (1 << 32)
sceneView.debugOptions = _debugOptions
sceneView.scene = scene.scene
sceneView.autorelease()
self.scene = scene
self.sceneView = sceneView
def viewWillAppear(self):
self.resetTracking()
def viewWillDisappear(self):
self.sceneView.session().pause()
def resetTracking(self):
configuration = ARWorldTrackingConfiguration.new()
self.sceneView.session().runWithConfiguration_(configuration)
class View(ui.View):
def __init__(self, *args, **kwargs):
ui.View.__init__(self, *args, **kwargs)
self.bg_color = 'maroon'
self.vc = ViewController()
self.objc_instance.addSubview_(self.vc.sceneView)
def will_close(self):
self.vc.viewWillDisappear()
if __name__ == '__main__':
view = View()
view.present(style='fullscreen', orientations=['portrait'])
SceneKit からARKit へ書き換え
前回のSceneKit では、objc_util
とPythonista3 のui.View
の連携としてView
class 内で完結をさせていました。
今回はARSCNView
のView 実装処理記述が多くなるためui.View
とは別にViewController
class を用意しました。
objc_util
としての、View 設定に関してはSCNView
と大きな変化はありません。
サンプルコードに乗っ取り、 UIView のライフサイクルを採用していますが
ViewController のあとに、View 呼んでいるので、なんじゃこりゃって感じなのですが。。。
debugOptions
のEnumeration Case
ARSCNView
のdebugOptions
もSCNView
のEnumeration Case を流用して呼び出せます。
''' debugOptions
OptionNone = 0
ShowPhysicsShapes = (1 << 0)
ShowBoundingBoxes = (1 << 1)
ShowLightInfluences = (1 << 2)
ShowLightExtents = (1 << 3)
ShowPhysicsFields = (1 << 4)
ShowWireframe = (1 << 5)
RenderAsWireframe = (1 << 6)
ShowSkeletons = (1 << 7)
ShowCreases = (1 << 8)
ShowConstraints = (1 << 9)
ShowCameras = (1 << 10)
ARSCNDebugOptionShowFeaturePoints = (1 << 30)
ARSCNDebugOptionShowWorldOrigin = (1 << 32)
'''
_debugOptions = (1 << 1) | (1 << 30) | (1 << 32)
sceneView.debugOptions = _debugOptions
-
ARSCNDebugOptionShowFeaturePoints
1 << 30
- トラッキングされるポイントを可視化
-
ARSCNDebugOptionShowWorldOrigin
(1 << 32)
- AR の座標軸を可視化
の2点がARSCNView
にて追加されています。
(1 << 30)
, (1 << 32)
の数値は:
def setDebugOptions(arscn):
# Work In Progress Here, I'm trying to decipher the arkit constants...
#val = ARSCNDebugOption.ARSCNDebugOptionShowWorldOrigin | ARSCNDebugOption.ARSCNDebugOptionShowFeaturePoints
val = int("fffffffffc000000", 16) # this value is a combination of ShowWorldOrigin and ShowFeaturePoints flags, but I can't isolate each flags....
print('Before calling setDebugOptions_(%s) : debugOptions=%s' %(hex(val), hex(arscn.debugOptions())))
arscn.setDebugOptions_(val)
print('After calling setDebugOptions_(%s) : debugOptions=%s' % (hex(val),hex(arscn.debugOptions())))
ここから無理やり割り出したように記憶しています。。。
main.py#L61 | Pythonista/main.py at master · Brun0oO/Pythonista
AR 機能を実行させる
SCNView
は、View へaddSubview_
すれば描画されました:
ui.View.objc_instance.addSubview_(SCNView)
ARSCNView
では「AR やるでー」と宣言しないと、カメラが起動しAR が始まりません。
「AR やるでー」の機能を司っているのはARSession
class です。
ARSession | Apple Developer Documentation
ObjCClass('ARSession')
と呼び出さなくとも、ARSCNView
でARSCNView.session
として、ARSession
を呼び出せます。
def resetTracking(self):
configuration = ARWorldTrackingConfiguration.new()
self.sceneView.session().runWithConfiguration_(configuration)
ARWorldTrackingConfigurat
でWorld
(リアカメラ)となり、フロントカメラを使った選択はここでおこないます。
起動しているかどうかは、ステータスバーの右上に緑の●
が付きます。
「AR おわったでー」と、宣言させないと、終了してくれないので:
def viewWillDisappear(self):
self.sceneView.session().pause()
class View(ui.View):
def __init__(self, *args, **kwargs):
ui.View.__init__(self, *args, **kwargs)
self.bg_color = 'maroon'
self.vc = ViewController()
self.objc_instance.addSubview_(self.vc.sceneView)
def will_close(self):
self.vc.viewWillDisappear()
Pythonista3 のui.View
、終了処理時のui.View.will_close
より、終了を呼んでいます。
世界にSceneKit のジオメトリを呼ぶ
SCNScene
内に、SceneKit 実装時と同様の処理で呼び出すことができます。
カメラ(SCNCamera
)はリアカメラとなるので、呼び出し不要です。
def setUpScene(self):
scene = SCNScene.scene()
scene_rootNode_addChildNode_ = scene.rootNode().addChildNode_
box = SCNBox.boxWithWidth_height_length_chamferRadius_(0.5, 0.5, 0.5, 0.02)
box.firstMaterial().diffuse().contents = UIColor.blueColor()
geometryNode = SCNNode.nodeWithGeometry_(box)
geometryNode.position = (0, -1.0, -1.0)
geometryNode.runAction_(
SCNAction.repeatActionForever_(
SCNAction.rotateByX_y_z_duration_(0.0, 0.2, 0.1, 0.3)))
scene_rootNode_addChildNode_(geometryNode)
self.scene = scene
box
のサイズがSCNBox.boxWithWidth_height_length_chamferRadius_(0.5, 0.5, 0.5, 0.08)
かなり小さいです。
大きすぎると、Material の設定によっては何も描画されないのでお気をつけください。
次回は
SceneKit からARKit への流れですと想定より簡単にAR が実装できたのではないかと思います。
SCNScene
内のbox
は、呼び出し時の位置から移動せず、カメラが動くと見切れてしまいました。
次回は、現実世界の情報をトラッキングして、良き位置に配置できるようにしていきます。
ここまで、読んでいただきありがとうございました。
せんでん
Discord
Pythonista3 の日本語コミュニティーがあります。みなさん優しくて、わからないところも親身に教えてくれるのでこの機会に覗いてみてください。
書籍
iPhone/iPad でプログラミングする最強の本。
その他
- サンプルコード
Pythonista3 Advent Calendar 2022 でのコードをまとめているリポジトリがあります。
コードのエラーや変なところや改善点など。ご指摘やPR お待ちしておりますー
なんしかガチャガチャしていますが、お気兼ねなくお声がけくださいませー
やれるか、やれないか。ではなく、やるんだけども、紹介説明することは尽きないと思うけど、締め切り守れるか?って話よ!(クズ)
— pome-ta (@pome_ta93) November 4, 2022
Pythonista3 Advent Calendar 2022 https://t.co/JKUxA525Pt #Qiita
- GitHub
基本的にGitHub にコードをあげているので、何にハマって何を実装しているのか観測できると思います。