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

マテリアル・マップ

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

マテリアルもマップも クラスを呼んで作り、プロパティに入れる だけです。 3ds Max 2022 の既定は フィジカルマテリアル(PhysicalMaterial)なので、本章もこれを中心にします。

マテリアルを作って割り当てる

m = rt.PhysicalMaterial(name="MyRed", base_color=rt.Color(200, 30, 30), roughness=0.4)
# MXS: m = PhysicalMaterial name:"MyRed" base_color:(color 200 30 30) roughness:0.4

b = rt.getNodeByName("Box001")
b.material = m                                  # MXS: b.material = m

for o in rt.selection:                          # 選択にまとめて割り当て
    o.material = m

rt.getNodeByName("Sphere001").material = None   # 割り当てを外す(MXS: $Sphere001.material = undefined)
  • 同じマテリアルを複数のノードに入れると 共有 されます(パラメータを変えるとそのマテリアルを持つすべてのノードに反映)。別々にしたいときは rt.copy(m) したものを入れます。
  • 旧来のスタンダードマテリアルは rt.StandardMaterial(diffuse=rt.Color(...)) です。プロパティ名がマテリアルの種類ごとに違う点に注意します。

マップ(テクスチャ)を差し込む

import os

tex_path = os.path.join(rt.getDir(rt.Name("maxroot")), "maps", "uvwunwrap", "UV_Checker.png")  # UV_Checker.png は 3ds Max に同梱の画像
bmp = rt.Bitmaptexture(fileName=tex_path, name="Checker_Tex")    # MXS: bitmapTexture fileName:"..."

m = rt.PhysicalMaterial(name="Textured")
m.base_color_map = bmp                     # ベースカラーに差す(MXS: m.base_color_map = bmp)
m.bump_map = rt.Noise(size=5)              # バンプにノイズ
m.bump_map_amt = 0.5
rt.showTextureMap(m, True)                 # ビューポートにテクスチャを表示(MXS: showTextureMap m on)
rt.redrawViews()                           # 表示直後は反映されないことがあるため明示的に再描画する

rt.getNodeByName("Box001").material = m
  • スタンダードマテリアルなら m.diffuseMap = bmp です。どのスロットがどんな名前かは rt.getPropNames(m) で確かめます。
  • タイリングなどの UV 設定はマップの coords の中にあります。pymxs では入れ子になったプロパティへの代入がそのまま効かないことがあるため、bmp.coords.U_Tiling = 2 ではなく bmp.setmxsprop("coords.U_Tiling", 2) と書くと確実です。

マテリアルエディタに入れる / 開く

m = rt.PhysicalMaterial(name="Slot1Mat")
rt.setMeditMaterial(1, m)              # スロット 1 に入れる(番号は 1 始まり)
rt.meditMaterials[0]                   # 添字で読むと 0 始まり(同じスロット 1)
rt.MatEditor.mode = rt.Name("basic")   # コンパクトエディタに切り替え(rt.Name("advanced") でスレート)
# rt.MatEditor.open()                  # エディタを開く
  • 同じスロットを 関数では 1、添字では 0 で指します。混ぜるとずれるので気を付けます。

マルチ/サブオブジェクトマテリアル

mm = rt.Multimaterial(numsubs=3, name="MyMulti")        # MXS: multimaterial numsubs:3
colors = [rt.Color(200, 40, 40), rt.Color(40, 200, 40), rt.Color(40, 40, 200)]
for i, c in enumerate(colors, start=1):
    rt.setSubMtl(mm, i, rt.PhysicalMaterial(base_color=c))   # サブマテリアルを設定(番号は 1 始まり)

subs = list(mm.materialList)                            # サブマテリアルの一覧
ids = list(mm.materialIDList)                           # それぞれのマテリアル ID

b = rt.getNodeByName("Box001")
rt.convertToPoly(b)
b.material = mm
rt.polyop.setFaceMatID(b, [1, 2], 2)                     # 面 1・2 を ID 2 にする
  • サブマテリアルの「番号(スロット順)」と「マテリアル ID」は別物です。面に割り当てるのは ID の方で、既定では 1, 2, 3… と並びます。

シーンのマテリアルを集める

for o in rt.objects:                                        # 準備: 各オブジェクトに別々のマテリアルを割り当てる
    o.material = rt.PhysicalMaterial(name="Mat_" + o.name)

used = []
for o in rt.objects:                                        # ノードに割り当てられているマテリアル
    if o.material is not None and o.material not in used:  # `in` は == で比べるので重複を除ける
        used.append(o.material)

names = sorted(m.name for m in used)                             # 使われているマテリアル名の一覧
all_physical = list(rt.getClassInstances(rt.PhysicalMaterial))   # シーン全体の PhysicalMaterial インスタンス一覧
by_mat = {}                                                 # マテリアル名 -> ノード名のリスト
for o in rt.objects:                                        # マテリアルごとにノードをまとめる
    if o.material is not None:
        by_mat.setdefault(o.material.name, []).append(o.name)
  • rt.sceneMaterials でもシーンのマテリアルを取れますが、更新のタイミングによって最新でないことがあります。rt.UpdateSceneMaterialLib() を呼ぶと強制的に更新できます。ノードから集めるのが確実です。
  • getClassInstances はマップにも使えます(次の項目)。target を省くとマテリアル エディタのスロットに入っているだけのマテリアルも対象になります。ノードの階層だけに絞りたいときは target=rt.rootNode を指定します。

テクスチャのパスを調べる / 付け替える

import os

rt.getNodeByName("Box001").material = rt.PhysicalMaterial(
    base_color_map=rt.Bitmaptexture(fileName=r"D:\old_project\maps\wood.png"))   # 準備: 見つからないパス

missing = []
for bmp in rt.getClassInstances(rt.Bitmaptexture):         # シーンの全ビットマップ(エディタのスロット内も含む)
    path = bmp.fileName
    if path and not os.path.exists(path):
        missing.append(path)

new_dir = rt.getDir(rt.Name("image"))                      # 付け替え先のフォルダ(例。実際は移動先のフォルダを指定する)
for bmp in rt.getClassInstances(rt.Bitmaptexture):
    if bmp.fileName in missing:
        bmp.fileName = os.path.join(new_dir, os.path.basename(bmp.fileName))
  • パスの存在確認や分解には Python の os.path(または pathlib)が使えます。
  • ビットマップのファイル名一覧だけでよければ rt.usedMaps() が最短です(ただしマテリアル エディタのスロットの中だけにあるマップは含まれません)。
  • テクスチャ以外の外部ファイル(IES・プロキシなど)も含めて一覧したいときは rt.AssetManager(→「ファイル・データ」章)を使います。

落とし穴まとめ

  • 同じマテリアルを入れると共有される。別々にするなら rt.copy(m)。
  • マテリアルの種類ごとにプロパティ名が違う(base_color_map / diffuseMap など)。
  • マテリアルエディタのスロットは、関数では 1 始まり、添字では 0 始まり。
  • サブマテリアルの番号とマテリアル ID は別物。

関連

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