リファレンス / 3ds Max / 3ds Max Python チートシート

モジュール管理・デバッグ

  • 3dsMax
  • Python
  • pymxs
  • チートシート
3ds Max Python チートシート の章一覧

ツールが大きくなってきたときの ファイルの分け方・読み込み方 と、知らない API を調べる方法・エラーの扱い方 です。

自作モジュールを import する(sys.path)

import os
import sys

tool_dir = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_pkg")      # 例: ツールを置くフォルダ
os.makedirs(tool_dir, exist_ok=True)
with open(os.path.join(tool_dir, "my_tool_util.py"), "w", encoding="utf-8") as f:
    f.write("def hello(name):\n    return 'Hello, ' + name\n")        # 例のモジュールを作る

if tool_dir not in sys.path:
    sys.path.append(tool_dir)            # import の検索先に足す(二重に足さない)

import my_tool_util
my_tool_util.hello("pymxs")              # 'Hello, pymxs'
  • 実際には、ツールのフォルダ(例: C:\tools\my_tool)を sys.path に足して、import で読み込みます。
  • 1 つのファイルが大きくなったら、UI(Qt)と処理(rt を呼ぶ部分)を別のモジュールに分けると、処理だけを使い回しやすくなります。

書き換えたモジュールを読み直す(reload)

import importlib
import my_tool_util

importlib.reload(my_tool_util)          # 3ds Max を再起動せずに変更を反映する
  • ⚠️ Python は一度 import したモジュールを覚えているので、ファイルを書き換えても もう一度 import するだけでは反映されません。開発中は importlib.reload します。
  • reload は指定したモジュールだけを読み直します。そのモジュールが別の自作モジュールを使っている場合は、依存先から順に reload します。

起動時に Python を実行する(スタートアップ)

3ds Max 2022 では、スタートアップフォルダに置いた .py は実行されません。MaxScript のスタートアップファイルから Python を呼びます。

ユーザのスタートアップフォルダ(rt.getDir(rt.Name("userStartupScripts")) で確認できる場所)に、次の .ms を置きます。

-- my_tool_startup.ms
python.ExecuteFile @"C:\tools\my_tool\startup.py"

起動のたびに実行したい Python(startup.py)の例:

import sys

TOOL_DIR = r"C:\tools\my_tool"
if TOOL_DIR not in sys.path:
    sys.path.append(TOOL_DIR)

# ここでマクロスクリプトの登録やコールバックの登録を行う
  • ツールバーやメニューに割り当てられるマクロスクリプトの登録は、起動のたびに行う処理としてここにまとめます(→「Qt UI」章)。

外部パッケージを入れる(pip)

3ds Max に同梱の Python([3ds Max のインストール先]\Python37\python.exe)に対して pip を使います。コマンドプロンプトで実行します。

cd "C:\Program Files\Autodesk\3ds Max 2022\Python37"
python.exe -m ensurepip --upgrade --user
python.exe -m pip install --user pillow
  • ensurepip は、pip が入っていない・古いときだけ実行すれば十分です(pip は 3ds Max 2021.1 Update 以降の Python に同梱されています)。
  • ⚠️ 3ds Max 同梱の python.exe を使う ことが大切です。PATH にある別の Python に入れても 3ds Max からは読めません。
  • --user を付けると、3ds Max のインストールフォルダではなくユーザのフォルダに入ります(管理者権限が要らず、3ds Max の更新でも消えません)。
  • 入ったか確かめるときは、3ds Max の中で import PIL を実行します。
  • プロジェクトごとにパッケージを分けたいときは仮想環境(venv)も使えます。環境を有効にしたコマンドプロンプトから 3dsmax.exe を起動します(公式ヘルプ「Virtual Environments and 3ds Max Python」)。

環境を確かめる

import sys

sys.version                  # Python のバージョン(3ds Max 2022 は 3.7 系)
sys.executable               # 実行中の Python
list(rt.maxVersion())        # 3ds Max のバージョン情報(先頭は内部番号。2022 は 24000)
rt.getDir(rt.Name("userScripts"))
  • sys.executable は、埋め込みの Python では 3dsmax.exe のパスになることがあります。Python 本体は [3ds Max のインストール先]\Python37\python.exe です。
  • list(rt.maxVersion()) の 8 番目の値(Python の添字では [7])は製品のリリース年です(2022 なら 2022)。3ds Max 2019.2 Update 以降で取得できます。

知らない API を調べる

b = rt.getNodeByName("Box001")
rt.execute("fn pyToString v = (v as string)")                # StringStream → 文字列に変換する MaxScript 関数を定義

props = [str(n) for n in rt.getPropNames(b)]                  # パラメータ名の一覧
# MXS: getPropNames b
rt.hasProperty(b, "height")                                   # そのプロパティがあるか
rt.classOf(b), rt.superClassOf(b)                              # クラスと大分類

ss = rt.StringStream("")
rt.showProperties(b, to=ss)                                    # 型付きの一覧(to= に出力先が必要)
# MXS: showProperties b to:ss
props_text = rt.pyToString(ss)

ss = rt.StringStream("")
rt.apropos("getVert", to=ss)                                   # 名前で関数・グローバルを探す(部分一致)
found = rt.pyToString(ss)

ss = rt.StringStream("")
rt.showInterfaces(rt.getNodeByName("Box001"), to=ss)           # そのオブジェクトが持つインタフェースと関数
  • ⚠️ showProperties / showClass / apropos / showInterfaces は、pymxs から呼ぶと リスナーには何も出ません。to= に rt.StringStream("") を渡して受け取ります(公式ヘルプの書き方)。
  • getPropNames は Name の配列を返すので、str() で Python の文字列にします。
  • pyToString は execute で定義した MaxScript 側の関数で、StringStream の中身を文字列にします。Python の str(ss) では StringStream:'...' という形になってしまうため、こうして中身だけを取り出します。
  • apropos は既定でパターンの前後に暗黙のワイルドカードが付く部分一致です。構造体の関数(polyop.getVert など)も見つかりますが、パターンには polyop. を付けず、名前の部分("getVert")だけを書きます("polyop.getVert" と書くと何も見つかりません)。implicitWild=False を指定するとこの暗黙のワイルドカードを外せます(パターンの末尾に * を付ければ先頭一致にできます)。
  • MaxScript のリスナー(MaxScript モード)で同じ関数を実行すると、そのまま表示されるので手早く調べられます。

例外を扱う

b = rt.getNodeByName("Box001")

try:
    b.noSuchProperty                               # 存在しないプロパティ
except AttributeError as e:
    prop_err = str(e)

try:
    rt.execute("myToolNoSuchFunction_xyz()")       # MaxScript 側のエラー
except RuntimeError as e:
    mxs_err = str(e)                               # MaxScript のエラーメッセージが入る
  • 存在しないプロパティを読むと AttributeError、MaxScript の実行中に起きたエラーは RuntimeError として Python に届きます。
  • ⚠️ with pymxs.undo(True): の中で起きた例外は、そこまでの変更を取り消したうえで 外に伝わりません(→「pymxs の基本」章)。エラーを知りたいときは undo の内側で try します。

ログを出す

import logging
import os
import traceback

log_path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool.log")
logger = logging.getLogger("my_tool")
if not logger.handlers:                                      # 何度実行してもハンドラを増やさない
    handler = logging.FileHandler(log_path, encoding="utf-8")
    handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
    logger.addHandler(handler)
    logger.setLevel(logging.INFO)

logger.info("start")
try:
    1 / 0
except Exception:
    logger.error(traceback.format_exc())                     # スタックトレースごと残す
    pymxs.print_("my_tool: エラーが起きました。ログを見てください\n", True)   # リスナーに赤字で出す
  • print() はリスナーに出ます。pymxs.print_(文字列, True) はエラー扱い(赤字)で出せます。
  • ユーザの手元で起きた問題を調べるには、ファイルに残すログの方が役に立ちます。logging は Python 標準です。

処理時間を測る

import time

t0 = time.perf_counter()
with pymxs.redraw(False):
    for i in range(100):
        rt.Box(pos=rt.Point3(i * 30, 0, 0))
# MXS: with redraw off (for i = 0 to 99 do Box pos:[i*30,0,0])
elapsed = time.perf_counter() - t0
print("{:.3f} 秒".format(elapsed))
  • 遅い処理は、まず時間を測ってから直します。pymxs では「rt の呼び出し回数を減らす」「再描画を止める」の 2 つが効くことが多いです(→「メッシュ・ポリゴン」章)。

落とし穴まとめ

  • sys.path に同じフォルダを何度も足さない。
  • 書き換えたモジュールは importlib.reload しないと反映されない。
  • スタートアップフォルダの .py は実行されない。.ms から python.ExecuteFile で呼ぶ。
  • pip は 3ds Max 同梱の python.exe で、--user を付けて使う。
  • showProperties などは to= に StringStream を渡さないと何も出ない。
  • pymxs.undo(True) の中の例外は外に伝わらない。

関連

← 3ds Max Python チートシート へ