New Addon: Import Autodesk .max #105013

Closed
Sebastian Sille wants to merge 136 commits from (deleted):nrgsille-import_max into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
Showing only changes of commit a6c89df258 - Show all commits

View File

@ -14,7 +14,7 @@
bl_info = { bl_info = {
"name": "Import Autodesk MAX (.max)", "name": "Import Autodesk MAX (.max)",
"author": "Sebastian Sille, Philippe Lagadec, Jens M. Plonka", "author": "Sebastian Sille, Philippe Lagadec, Jens M. Plonka",
"version": (1, 1, 0), "version": (1, 1, 2),
"blender": (4, 0, 0), "blender": (4, 0, 0),
"location": "File > Import", "location": "File > Import",
"description": "Import 3DSMAX meshes & materials", "description": "Import 3DSMAX meshes & materials",
@ -54,6 +54,14 @@ class Import_max(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
soft_min=0.0, soft_max=10000.0, soft_min=0.0, soft_max=10000.0,
default=1.0, default=1.0,
) )
use_material: bpy.props.BoolProperty(name="Materials",
description="Import the materials of the objects",
default=True,
)
use_uv_mesh: bpy.props.BoolProperty(name="UV Mesh",
description="Import texture coordinates as mesh objects",
default=False,
)
use_apply_matrix: bpy.props.BoolProperty(name="Apply Matrix", use_apply_matrix: bpy.props.BoolProperty(name="Apply Matrix",
description="Use matrix to transform the objects", description="Use matrix to transform the objects",
default=False, default=False,
@ -70,6 +78,35 @@ class Import_max(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
pass pass
class MAX_PT_import_include(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
bl_label = "Include"
bl_parent_id = "FILE_PT_operator"
@classmethod
def poll(cls, context):
sfile = context.space_data
operator = sfile.active_operator
return operator.bl_idname == "IMPORT_AUTODESK_OT_max"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = True
sfile = context.space_data
operator = sfile.active_operator
layrow = layout.row(align=True)
layrow.prop(operator, "use_material")
layrow.label(text="", icon='MATERIAL' if operator.use_material else 'SHADING_TEXTURE')
layrow = layout.row(align=True)
layrow.prop(operator, "use_uv_mesh")
layrow.label(text="", icon='UV' if operator.use_uv_mesh else 'GROUP_UVS')
class MAX_PT_import_transform(bpy.types.Panel): class MAX_PT_import_transform(bpy.types.Panel):
bl_space_type = 'FILE_BROWSER' bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS' bl_region_type = 'TOOL_PROPS'
@ -105,12 +142,14 @@ def menu_func(self, context):
def register(): def register():
bpy.utils.register_class(Import_max) bpy.utils.register_class(Import_max)
bpy.utils.register_class(MAX_PT_import_include)
bpy.utils.register_class(MAX_PT_import_transform) bpy.utils.register_class(MAX_PT_import_transform)
bpy.types.TOPBAR_MT_file_import.append(menu_func) bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister(): def unregister():
bpy.types.TOPBAR_MT_file_import.remove(menu_func) bpy.types.TOPBAR_MT_file_import.remove(menu_func)
bpy.utils.unregister_class(MAX_PT_import_transform) bpy.utils.unregister_class(MAX_PT_import_transform)
bpy.utils.unregister_class(MAX_PT_import_include)
bpy.utils.unregister_class(Import_max) bpy.utils.unregister_class(Import_max)
@ -1205,7 +1244,8 @@ def get_color(colors, idx):
def get_value(colors, idx): def get_value(colors, idx):
prop = get_property(colors, idx) prop = get_property(colors, idx)
if (prop is not None): if (prop is not None):
val, offset = get_float(prop.data, 15) siz = 15 if (len(prop.data) > 23) else 6
val, offset = get_float(prop.data, siz)
return val return val
return None return None
@ -1288,7 +1328,7 @@ def adjust_matrix(obj, node):
return plc return plc
def create_shape(context, pts, indices, node, key, mtx, mat): def create_shape(context, pts, indices, node, key, mtx, mat, umt):
name = node.get_first(TYP_NAME).data name = node.get_first(TYP_NAME).data
shape = bpy.data.meshes.new(name) shape = bpy.data.meshes.new(name)
if (key is not None): if (key is not None):
@ -1297,13 +1337,13 @@ def create_shape(context, pts, indices, node, key, mtx, mat):
if (pts): if (pts):
loopstart = [] loopstart = []
looplines = loop = 0 looplines = loop = 0
nbr_faces = len(indices) nb_faces = len(indices)
for fid in range(nbr_faces): for fid in range(nb_faces):
polyface = indices[fid] polyface = indices[fid]
looplines += len(polyface) looplines += len(polyface)
shape.vertices.add(len(pts) // 3) shape.vertices.add(len(pts) // 3)
shape.loops.add(looplines) shape.loops.add(looplines)
shape.polygons.add(nbr_faces) shape.polygons.add(nb_faces)
shape.vertices.foreach_set("co", pts) shape.vertices.foreach_set("co", pts)
for vtx in indices: for vtx in indices:
loopstart.append(loop) loopstart.append(loop)
@ -1317,8 +1357,9 @@ def create_shape(context, pts, indices, node, key, mtx, mat):
shape.update() shape.update()
obj = bpy.data.objects.new(name, shape) obj = bpy.data.objects.new(name, shape)
context.view_layer.active_layer_collection.collection.objects.link(obj) context.view_layer.active_layer_collection.collection.objects.link(obj)
adjust_material(obj, mat)
obj.matrix_world = mtx obj.matrix_world = mtx
if (umt):
adjust_material(obj, mat)
return True return True
return True return True
@ -1424,11 +1465,14 @@ def get_poly_data(chunk):
return polylist return polylist
def create_editable_poly(context, node, msh, mat, mtx): def create_editable_poly(context, node, msh, mat, mtx, umt, uvm):
coords = point3i = point4i = point6i = pointNi = None coords = point4i = point6i = pointNi = None
name = node.get_first(TYP_NAME).data name = node.get_first(TYP_NAME).data
poly = msh.get_first(0x08FE) poly = msh.get_first(0x08FE)
created = False created = False
lidx = []
lcrd = []
lply = []
if (poly): if (poly):
for child in poly.children: for child in poly.children:
if (child.types == 0x0100): if (child.types == 0x0100):
@ -1437,20 +1481,34 @@ def create_editable_poly(context, node, msh, mat, mtx):
point6i = child.data point6i = child.data
elif (child.types == 0x011A): elif (child.types == 0x011A):
point4i = calc_point_3d(child) point4i = calc_point_3d(child)
elif (child.types == 0x0310):
pointNi = child.data
elif (child.types == 0x0124):
lidx.append(get_long(child.data, 0)[0])
elif (child.types == 0x0128):
lcrd.append(calc_point_float(child.data))
elif (child.types == 0x012B):
lply.append(get_poly_data(child))
if (point4i is not None): if (point4i is not None):
vertex = get_poly_4p(point4i) vertex = get_poly_4p(point4i)
if (len(vertex) > 0): if (len(vertex) > 0):
for key, ngons in vertex.items(): for key, ngons in vertex.items():
created |= create_shape(context, coords, ngons, node, key, mtx, mat) created |= create_shape(context, coords, ngons, node, key, mtx, mat, umt)
else: else:
created = True created = True
elif (point6i is not None): elif (point6i is not None):
ngons = get_poly_6p(point6i) ngons = get_poly_6p(point6i)
created = create_shape(context, coords, ngons, node, None, mtx, mat) created = create_shape(context, coords, ngons, node, None, mtx, mat, umt)
elif (pointNi is not None):
ngons = get_poly_5p(pointNi)
created = create_shape(context, coords, ngons, node, None, mtx, mat, umt)
if (uvm and len(lidx) > 0):
for i in range(len(lidx)):
created |= create_shape(context, lcrd[i], lply[i], node, lidx[i], mtx, mat, umt)
return created return created
def create_editable_mesh(context, node, msh, mat, mtx): def create_editable_mesh(context, node, msh, mat, mtx, umt):
name = node.get_first(TYP_NAME).data name = node.get_first(TYP_NAME).data
poly = msh.get_first(0x08FE) poly = msh.get_first(0x08FE)
created = False created = False
@ -1459,15 +1517,15 @@ def create_editable_mesh(context, node, msh, mat, mtx):
clsid_chunk = poly.get_first(0x0912) clsid_chunk = poly.get_first(0x0912)
coords = get_point_array(vertex_chunk.data) coords = get_point_array(vertex_chunk.data)
ngons = get_poly_5p(clsid_chunk.data) ngons = get_poly_5p(clsid_chunk.data)
created = create_shape(context, coords, ngons, node, None, mtx, mat) created = create_shape(context, coords, ngons, node, None, mtx, mat, umt)
return created return created
def create_shell(context, node, shell, mat, mtx): def create_shell(context, node, shell, mat, mtx, umt):
name = node.get_first(TYP_NAME).data name = node.get_first(TYP_NAME).data
refs = get_references(shell) refs = get_references(shell)
msh = refs[-1] msh = refs[-1]
created = create_editable_mesh(context, node, msh, mat, mtx) created = create_editable_mesh(context, node, msh, mat, mtx, umt)
return created return created
@ -1477,16 +1535,16 @@ def create_skipable(context, node, skip):
return True return True
def create_mesh(context, node, msh, mtx, mat): def create_mesh(context, node, msh, mtx, mat, umt, uvm):
created = False created = False
uid = get_guid(msh) uid = get_guid(msh)
msh.geometry = None msh.geometry = None
if (uid == 0x0E44F10B3): if (uid == 0x0E44F10B3):
created = create_editable_mesh(context, node, msh, mat, mtx) created = create_editable_mesh(context, node, msh, mat, mtx, umt)
elif (uid == 0x192F60981BF8338D): elif (uid == 0x192F60981BF8338D):
created = create_editable_poly(context, node, msh, mat, mtx) created = create_editable_poly(context, node, msh, mat, mtx, umt, uvm)
elif (uid in {0x2032, 0x2033}): elif (uid in {0x2032, 0x2033}):
created = create_shell(context, node, msh, mat, mtx) created = create_shell(context, node, msh, mat, mtx, umt)
else: else:
skip = SKIPPABLE.get(uid) skip = SKIPPABLE.get(uid)
if (skip is not None): if (skip is not None):
@ -1494,7 +1552,7 @@ def create_mesh(context, node, msh, mtx, mat):
return created, uid return created, uid
def create_object(context, node, mscale, transform): def create_object(context, node, mscale, usemat, uvmesh, transform):
parent = get_node_parent(node) parent = get_node_parent(node)
node.parent = parent node.parent = parent
name = get_node_name(node) name = get_node_name(node)
@ -1509,26 +1567,26 @@ def create_object(context, node, mscale, transform):
mtx = create_matrix(prs) @ mscale mtx = create_matrix(prs) @ mscale
else: else:
mtx = mscale mtx = mscale
created, uid = create_mesh(context, node, msh, mtx, mat) created, uid = create_mesh(context, node, msh, mtx, mat, usemat, uvmesh)
def make_scene(context, mscale, transform, parent, level=0): def make_scene(context, mscale, usemat, uvmesh, transform, parent, level=0):
for chunk in parent.children: for chunk in parent.children:
if (isinstance(chunk, SceneChunk)): if (isinstance(chunk, SceneChunk)):
if ((get_guid(chunk) == 0x0001) and (get_super_id(chunk) == 0x0001)): if ((get_guid(chunk) == 0x0001) and (get_super_id(chunk) == 0x0001)):
try: try:
create_object(context, chunk, mscale, transform) create_object(context, chunk, mscale, usemat, uvmesh, transform)
except Exception as exc: except Exception as exc:
print('ImportError:', exc, chunk) print('ImportError:', exc, chunk)
def read_scene(context, maxfile, filename, mscale, transform): def read_scene(context, maxfile, filename, mscale, usemat, uvmesh, transform):
global SCENE_LIST global SCENE_LIST
SCENE_LIST = read_chunks(maxfile, 'Scene', filename+'.Scn.bin', containerReader=SceneChunk) SCENE_LIST = read_chunks(maxfile, 'Scene', filename+'.Scn.bin', containerReader=SceneChunk)
make_scene(context, mscale, transform, SCENE_LIST[0], 0) make_scene(context, mscale, usemat, uvmesh, transform, SCENE_LIST[0], 0)
def read(context, filename, mscale, transform): def read(context, filename, mscale, usemat, uvmesh, transform):
if (is_maxfile(filename)): if (is_maxfile(filename)):
maxfile = ImportMaxFile(filename) maxfile = ImportMaxFile(filename)
prop = maxfile.getproperties('\x05DocumentSummaryInformation', convert_time=True, no_conversion=[10]) prop = maxfile.getproperties('\x05DocumentSummaryInformation', convert_time=True, no_conversion=[10])
@ -1538,15 +1596,17 @@ def read(context, filename, mscale, transform):
read_directory(maxfile, filename) read_directory(maxfile, filename)
read_class_directory(maxfile, filename) read_class_directory(maxfile, filename)
read_video_postqueue(maxfile, filename) read_video_postqueue(maxfile, filename)
read_scene(context, maxfile, filename, mscale, transform) read_scene(context, maxfile, filename, mscale, usemat, uvmesh, transform)
else: else:
print("File seems to be no 3D Studio Max file!") print("File seems to be no 3D Studio Max file!")
def load(operator, context, filepath="", scale_objects=1.0, use_apply_matrix=False, global_matrix=None): def load(operator, context, filepath="", scale_objects=1.0, use_material=True,
use_uv_mesh=False, use_apply_matrix=False, global_matrix=None):
mscale = mathutils.Matrix.Scale(scale_objects, 4) mscale = mathutils.Matrix.Scale(scale_objects, 4)
if global_matrix is not None: if global_matrix is not None:
mscale = global_matrix @ mscale mscale = global_matrix @ mscale
read(context, filepath, mscale, transform=use_apply_matrix)
read(context, filepath, mscale, usemat=use_material, uvmesh=use_uv_mesh, transform=use_apply_matrix)
return {'FINISHED'} return {'FINISHED'}