← Back to 3ds-max
Export 3ds Max Materials and Geometry Assignments to JSON using Python
07 Feb 26 (13d ago)
Nothing special. Just exports material and geometry assignments to JSON to check if the materials I recreated in Maya, Cinema4D and Blender uses the same textures as the original.
It saves the JSON right next to script.
There is a progress with OpenPBR Material but for the last 2 decades there is really no easy way to converting materials from one DCC to another even with the same renderer.
import pymxs
import json
import os
import sys
def get_clean_name(name_str):
"""
Helper: Converts string to lowercase and replaces spaces with underscores.
"""
if not name_str: return ""
return name_str.lower().replace(" ", "_")
def get_assigned_objects(mat):
"""
Returns a list of scene object names that have this material assigned.
"""
rt = pymxs.runtime
obj_list = []
# refs.dependents gets everything referencing this material (objects, modifiers, etc.)
# immediateOnly=False ensures we find objects even if there's a wrapper in between.
deps = rt.refs.dependents(mat, immediateOnly=False)
for d in deps:
# 1. Check if it is a valid scene node (Object, Light, Camera, etc.)
if rt.isValidNode(d):
# 2. Check if it is Geometry (excludes Lights/Cameras/Helpers if you only want meshes)
# SuperClassID 3 == GeometryClass
if rt.superClassOf(d) == rt.GeometryClass:
# 3. Verify the material is actually assigned to this object
# (avoids listing objects that just use the material in a modifier)
if d.material == mat:
obj_list.append(d.name)
# Remove duplicates just in case
return list(set(obj_list))
def get_node_value(node):
rt = pymxs.runtime
# 1. MultiTile (Tile 1 only)
if rt.isProperty(node, "filenames"):
try:
f_list = node.filenames
if f_list and len(f_list) > 0:
return f"UDIM Sequence (Tile1): {f_list[0]}"
except:
pass
# 2. Bitmaps
if rt.isProperty(node, "filename"):
return f"Bitmap: {node.filename}"
# 3. Colors
elif rt.isProperty(node, "color"):
c = node.color
return f"Color: RGB({int(c.r)}, {int(c.g)}, {int(c.b)})"
# 4. HDRI
elif rt.isProperty(node, "HDRIMapName"):
return f"HDRI: {node.HDRIMapName}"
return None
def get_texture_data(map_node):
rt = pymxs.runtime
raw_name = map_node.name
node_data = {
"name": raw_name,
"name_02": get_clean_name(raw_name),
"type": str(rt.classOf(map_node)),
"value": get_node_value(map_node),
"connections": {}
}
try:
num_sub_tex = rt.getNumSubTexmaps(map_node)
except:
num_sub_tex = 0
if num_sub_tex > 0:
is_multitile = "MultiTile" in str(rt.classOf(map_node))
loop_range = range(1, 2) if is_multitile else range(1, num_sub_tex + 1)
for i in loop_range:
sub_tex = rt.getSubTexmap(map_node, i)
if sub_tex is not None and sub_tex != rt.undefined:
slot_name = rt.getSubTexmapSlotName(map_node, i)
node_data["connections"][slot_name] = get_texture_data(sub_tex)
return node_data
def get_material_data(mat):
rt = pymxs.runtime
raw_name = mat.name
mat_data = {
"name": raw_name,
"name_02": get_clean_name(raw_name),
"type": str(rt.classOf(mat)),
"assigned_geometries": get_assigned_objects(mat), # <--- NEW FIELD
"sub_materials": [],
"maps": {}
}
# Recurse Sub-Materials
try:
num_subs = rt.getNumSubMtls(mat)
except:
num_subs = 0
if num_subs > 0:
for i in range(1, num_subs + 1):
sub_mat = rt.getSubMtl(mat, i)
if sub_mat:
mat_data["sub_materials"].append(get_material_data(sub_mat))
# Texture Recursion
try:
num_maps = rt.getNumSubTexmaps(mat)
except:
num_maps = 0
if num_maps > 0:
for i in range(1, num_maps + 1):
tex_node = rt.getSubTexmap(mat, i)
if tex_node is not None and tex_node != rt.undefined:
slot_name = rt.getSubTexmapSlotName(mat, i)
mat_data["maps"][slot_name] = get_texture_data(tex_node)
return mat_data
def save_to_json():
rt = pymxs.runtime
scene_data = []
print("--- GATHERING DATA WITH GEOMETRIES ---")
for mat in rt.scenematerials:
if mat:
scene_data.append(get_material_data(mat))
# --- SAVE TO FILE ---
try:
script_dir = os.path.dirname(os.path.realpath(__file__))
except NameError:
script_dir = "C:\\Temp"
if not os.path.exists(script_dir):
os.makedirs(script_dir)
file_name = "scene_materials.json"
full_path = os.path.join(script_dir, file_name)
print(f"--- SAVING JSON TO: {full_path} ---")
try:
with open(full_path, 'w', encoding='utf-8') as f:
json.dump(scene_data, f, ensure_ascii=False, indent=4)
print("Successfully saved.")
except Exception as e:
print(f"Error saving file: {e}")
# Execute
save_to_json()