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

ファイル・データ

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

ファイルまわりは Python の標準ライブラリが一番強い ところです。 パス操作は os.path / pathlib、データの読み書きは json / csv を使い、3ds Max 固有のこと(シーンの保存・読み込み・書き出し)だけを rt で行います。

よく使うパスを取る

import os

scene_dir = rt.maxFilePath                    # 開いているシーンのフォルダ(未保存なら "")
scene_name = rt.maxFileName                   # ファイル名(未保存なら "")
full = os.path.join(scene_dir, scene_name)    # フルパス

temp_dir = rt.getDir(rt.Name("temp"))         # MXS: getDir #temp
export_dir = rt.getDir(rt.Name("export"))     # 書き出しの既定フォルダ
render_dir = rt.getDir(rt.Name("renderoutput"))
project = rt.pathConfig.getCurrentProjectFolder()   # プロジェクトフォルダ
  • getDir には #scene / #export / #import / #renderoutput / #temp / #maxroot / #userScripts などの Name を渡します。
  • 未保存のシーンでは maxFilePath も maxFileName も空文字です。保存先を決める処理では、まずこれを確かめます。

pathlib でパスを組み立てる

from pathlib import Path

out_dir = Path(rt.getDir(rt.Name("temp"))) / "my_tool" / "exports"
out_dir.mkdir(parents=True, exist_ok=True)        # フォルダをまとめて作る

base = Path(rt.maxFileName).stem or "untitled"     # 拡張子を除いた名前
fbx_path = out_dir / (base + ".fbx")
fbx_str = str(fbx_path)   # rt の関数には str にして渡す
  • ⚠️ rt の関数に Path オブジェクトをそのまま渡すと受け付けないことがあります。str() にしてから渡す のが安全です。
  • Python 3 の文字列は Unicode なので、日本語を含むパスもそのまま扱えます。

シーンを保存する / 開く / リセットする

import os

path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_scene.max")
rt.saveMaxFile(path, quiet=True)                  # MXS: saveMaxFile path quiet:true
rt.resetMaxFile(rt.Name("noPrompt"))              # 新規シーン(確認なし)
rt.loadMaxFile(path, quiet=True)                  # MXS: loadMaxFile path quiet:true
  • ⚠️ saveMaxFile / loadMaxFile / resetMaxFile(rt.Name("noPrompt")) は 確認なしで上書き・破棄 します。ユーザの作業中のシーンに対して使う前に、rt.checkForSave()(変更があれば保存を尋ねる)を呼ぶと親切です。キャンセルされると False を返すので、if rt.checkForSave(): ... のように使います。
  • quiet=True を付けると、足りないプラグインや外部ファイルの警告ダイアログを出しません(バッチ処理向け)。

選んだノードだけを別ファイルに保存する

import os

path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_parts.max")
rt.saveNodes([rt.getNodeByName("Box001")], path, quiet=True)   # MXS: saveNodes #($Box001) path
names = list(rt.getMAXFileObjectNames(path, quiet=True))        # シーンを開かずに、ファイル内のオブジェクト名を読む(名前順の文字列の配列)

別のシーンから取り込む(マージ)

import os

path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_parts.max")
rt.saveNodes([rt.getNodeByName("Box001")], path, quiet=True)    # 準備: 取り込み元を作る

rt.mergeMAXFile(path, ["Box001"], rt.Name("autoRenameDups"), quiet=True)   # 同名は自動でリネーム
# MXS: mergeMAXFile path #("Box001") #autoRenameDups quiet:true
merged = list(rt.getLastMergedNodes())                                    # 取り込んだノード
# MXS: getLastMergedNodes()
  • 2 つ目の引数に名前のリストを渡すと、そのオブジェクトだけを取り込みます(省くと全部)。
  • 重複の扱いは #autoRenameDups(リネーム)/ #skipDups(取り込まない)/ #deleteOldDups(既存を消す)などの Name を位置引数で渡します。quiet=True のようなキーワード引数ではなく、rt.Name("...") をそのまま位置引数で並べる点に注意します。

FBX / OBJ に書き出す・読み込む

import os

rt.pluginManager.loadClass(rt.FBXEXP)               # MXS: pluginManager.loadClass FBXEXP
rt.FBXExporterSetParam("Animation", False)          # FBX の書き出し設定(MXS: FBXExporterSetParam "Animation" false)
rt.FBXExporterSetParam("UpAxis", "Y")

path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_export.fbx")
rt.select(rt.getNodeByName("Box001"))
ok = rt.exportFile(path, rt.Name("noPrompt"), selectedOnly=True, using=rt.FBXEXP)
# MXS: exportFile path #noPrompt selectedOnly:true using:FBXEXP

rt.resetMaxFile(rt.Name("noPrompt"))
rt.pluginManager.loadClass(rt.FBXIMP)
rt.importFile(path, rt.Name("noPrompt"), using=rt.FBXIMP)   # 読み込み
  • FBXExporterSetParam を使うには、FBX 書き出しプラグインがメモリにロードされている必要があります。ロードされていない場合は rt.pluginManager.loadClass(rt.FBXEXP) でロードします。
  • #noPrompt を付けないと、書き出し設定のダイアログが開きます。設定は事前に FBXExporterSetParam で済ませておきます。
  • FBXExporterSetParam で変えた設定は、UI から書き出すときにも残ることがあります。ユーザの設定を変えたくないときは、rt.FBXExporterSetParam("PushSettings") で退避してから設定し、終わったら rt.FBXExporterSetParam("PopSettings") で戻します。
  • 書き出し形式は拡張子で決まりますが、using= でプラグインのクラスを明示すると確実です(OBJ なら rt.ObjExp)。

シーンの情報を JSON に書き出す / 読み戻す

import json
import os

data = []
for o in rt.objects:
    p = o.pos
    data.append({
        "name": o.name,
        "class": str(rt.classOf(o)),
        "pos": [p.x, p.y, p.z],
        "material": o.material.name if o.material else None,
    })

path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_scene.json")
with open(path, "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# 読み戻して位置を復元する
with open(path, encoding="utf-8") as f:
    for item in json.load(f):
        node = rt.getNodeByName(item["name"])
        if node is not None:
            node.pos = rt.Point3(*item["pos"])
  • ⚠️ json がそのまま書けるのは Python の型だけです。Point3 や Color は 成分を取り出して list や数値にする、クラスや Name は str() にする、という変換が必要です。
  • ensure_ascii=False にすると日本語の名前がそのまま読める形で保存されます。

CSV に書き出す(Excel で開く)

import csv
import os

path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_report.csv")
with open(path, "w", encoding="utf-8-sig", newline="") as f:     # utf-8-sig = BOM 付き
    w = csv.writer(f)
    w.writerow(["名前", "クラス", "三角形面数"])
    for o in rt.geometry:
        w.writerow([o.name, str(rt.classOf(o)), rt.getPolygonCount(o)[0]])
  • getPolygonCount() はノードをメッシュオブジェクトに変換した場合の面の数を返します。メッシュは三角形の面だけで構成されるので、実質的に三角形の数になります。
  • Excel で日本語を文字化けさせずに開くには BOM 付き UTF-8(utf-8-sig) で書きます。
  • newline="" を付けないと、Windows では行の間に空行が入ります。

ファイルを選ぶダイアログ

path = rt.getSaveFileName(caption="書き出し先", types="JSON (*.json)|*.json|All (*.*)|*.*|")
# MXS: getSaveFileName caption:"書き出し先" types:"JSON (*.json)|*.json|All (*.*)|*.*|"
if path:                                                  # キャンセルなら None
    print(path)

src = rt.getOpenFileName(caption="読み込むファイル", types="FBX (*.fbx)|*.fbx|")
folder = rt.getSavePath(caption="フォルダを選ぶ")          # フォルダ選択
  • types= は "説明1|パターン1|説明2|パターン2|...|" の形式で、先頭と末尾を含めて各要素を | で区切ります。末尾の | を忘れないようにします。
  • キャンセルされると None が返ります(MaxScript の undefined)。
  • Qt の UI を作っているなら QtWidgets.QFileDialog を使っても構いません(→「Qt UI」章)。

シーンの外部ファイルを一覧する

files = []
for i in range(1, rt.AssetManager.getNumAssets() + 1):      # 番号は 1 始まり
    asset = rt.AssetManager.getAssetByIndex(i)
    files.append(asset.getFullFilePath())
# MXS: for i = 1 to AssetManager.GetNumAssets() do (local a = AssetManager.GetAssetByIndex i; append files a.GetFullFilePath())
  • AssetManager が管理する全アセットが対象です。テクスチャ・IES・XRef だけでなく、シーン自身やレンダー出力先なども含まれます。種類を絞りたいときは asset.getType()(#Bitmap / #XRef / #RenderOutput など)で判定します。
  • ファイル名だけでなくフルパスが欲しい場合は asset.getFileName() ではなく asset.getFullFilePath() を使います(getFileName() はフルパスとは限りません)。
  • テクスチャのファイル名だけを集めたいなら、rt.usedMaps() の方が短く書けます。

落とし穴まとめ

  • 未保存のシーンは maxFilePath / maxFileName が空文字。
  • saveMaxFile / loadMaxFile / resetMaxFile(rt.Name("noPrompt")) は確認なしで上書き・破棄する。
  • rt の関数には Path ではなく str を渡す。
  • Point3 などは JSON にそのまま書けない。成分に分けてから書く。
  • Excel 向けの CSV は utf-8-sig、newline=""。

関連

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