Dynamo で使う CPython の注意点
英語版の資料がありました…
背景
Revit や Civil 3D に搭載されている Dynamo は、2021 版では 2.5 (Civil 3D) or 2.6 (Revit) だったものが、2022 版では一気に 2.10 までアップグレードされました。
その間にどんな改善があったかは、各バージョンのリリースノートで確認ができまして、例えば 2.9 は下記にあります。
2021 版から 2022 版への大きな機能改善と言うと、2.7 でリリースされた CPython への対応、および 2.9 でリリースされた Node Autocomplete が挙げられます。
Dynamo 上で使用する上で、IronPython2 と CPython3 では、公式の注意書きにある「Python 2 と Python 3 で記法が違う」ということ以外にも違いがあります。
ここでは、実際にコードを移管してみて上手く動かなかったところを随時、まとめていきます。
型の取得
例えば、取得した obj
の型が Line
かどうかを判定したい時、IronPython2 では
isLineType = obj.GetType() == Line
と書けば、実行時に「Line
は型を表す」と勝手に判断してくれました。しかし Cpython3 では
isLineType = obj.GetType() == clr.GetClrType(Line)
と明示的に表現する必要があります。C# で typeof(Line)
と書くような感じです。
ホントは IronPython2 でも clr.GetClrType(Line)
と書くべきですね。
With
ステートメント中のエラーメッセージ
Dynamo for Civil 3D では、Python の with
(C# の using
)を書くことが多いです。
LockDocument()
や StartTransaction()
など、安全にドキュメントにアクセスするための
下準備が色々あり、それらをいちいち Dispose()
するのが面倒だからです(多分)。
この with
内部で意図的にエラーを発生させてみます。たとえば
rightList = ['hoge', 'fuga']
wrongList = None
result = someFuncWithList(wrongList)
みたいに、「for loop を回そうとしたけど NoneType
受け取った、どうしよう」
みたいなエラーを発生させます(正式な名称が不明)。
エラーが出ると、CPython3 ではこう書かれます。
No method matches given arguments for OnExit:
(<class 'Autodesk.AutoCAD.ApplicationServices.DocumentLock'>,
<class 'type'>, <class 'TypeError'>, <class 'traceback'>)
[' File "<string>", line xxx, in <module>\n']
DocumentLock
クラスにエラーが発生しているので、LockDocument()
が原因のように思えます。
しかし、IronPython2 ではこう書かれます。
IronPythonEvaluatorEvaluateIronPythonScript 操作に失敗しました。
Traceback (most recent call last):
File "<string>", line xxx, in <module>
File "<string>", line xxx, in Func
TypeError: iteration over non-sequence of type NoneType
「あぁ、リストを期待されていたのに NoneType
渡しちゃったな」というのが分かります。
というわけで、with
節内のエラーは、まず with
節から出して判断するのが良さそうです。
with
節から出すと CPython3 も IronPython2 と同じエラーメッセージが出ます。
ちなみに、この違いがなぜ発生するかは全く分かりません…。
リスト処理(API 直アクセス)
例えば、下記のコードを実行します。
ここでは Civil 3D 線形スタイルの、ObjectId の「集合」を取得します。
alignmentStyleIds = CivilApplication.ActiveDocument.Styles.AlignmentStyles
例は何でも良いですが、Revit や Civil 3D はこのように「集合」を取得する場合があります。
alignmentStyleIds
の型が何であるかは、下記のように書くと取得できます。
OUT = type(alignId)
見てみると、IronPython2 の場合の型は IronPython.Runtime.Types.PythonType
で、
CPython3 の場合は Autodesk.Civil.DatabaseServices.Styles.AlignmentStyleCollection
です。
AlignmentStyleCollection
は IEnumerable<>
を継承しているので、
for loop で繰り返し計算は出来ますが、alignmentStyleIds[0]
のように
インデックスを指定して値を取得することが出来ません。
この場合、下記のような内包表記で集合を展開してあげます。
alignmentStyleIdsPy = [item for item in alignmentStyleIds]
そうすると、型が class 'list'
になり、インデックスで要素を取得できます。
リスト処理(Dynamo ノード)
例えば、下記のコードを実行します。
list = [Geometry.Intersect(item, IN[1]) for item in IN[0]]
IN[0]
の型は Geometry[]
で、IN[1]
の型は Geometry
です。
list
の型が何であるかは、下記のように書くと取得できます。
OUT = type(list)
見てみると、IronPython2 の場合の型は IronPython.Runtime.Types.PythonType
で、CPython3 の場合の型は class 'list'
です。
これに対して Dynamo ノードでリスト処理(e.g. List.Flatten)を行うと、IronPython2 の型は IronPython.Runtime.Types.PythonType
のままですが、CPython3 の型は System.Collections.Generic.List[System.Object]
に変わります。
このように、リスト処理の結果として型が変わることで、上手くプログラムが回らなくなる場合があります。その場合、Pythonic な書き方をすると型を変えずに済みます。
例えば List.Flatten の場合は、
× List.Flatten(list, -1)
○ [item for sublist in list for item in sublist]
とすると、型が class 'list'
のままになり、上手くスクリプトが回ります。
参考:
Discussion