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 = {
"name": "Import Autodesk MAX (.max)",
"author": "Sebastian Sille, Philippe Lagadec, Jens M. Plonka",
"version": (1, 1, 0),
"version": (1, 1, 2),
"blender": (4, 0, 0),
"location": "File > Import",
"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,
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",
description="Use matrix to transform the objects",
default=False,
@ -70,6 +78,35 @@ class Import_max(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
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):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'TOOL_PROPS'
@ -105,12 +142,14 @@ def menu_func(self, context):
def register():
bpy.utils.register_class(Import_max)
bpy.utils.register_class(MAX_PT_import_include)
bpy.utils.register_class(MAX_PT_import_transform)
bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister():
bpy.types.TOPBAR_MT_file_import.remove(menu_func)
bpy.utils.unregister_class(MAX_PT_import_transform)
bpy.utils.unregister_class(MAX_PT_import_include)
bpy.utils.unregister_class(Import_max)
@ -1205,7 +1244,8 @@ def get_color(colors, idx):
def get_value(colors, idx):
prop = get_property(colors, idx)
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 None
@ -1288,7 +1328,7 @@ def adjust_matrix(obj, node):
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
shape = bpy.data.meshes.new(name)
if (key is not None):
@ -1297,13 +1337,13 @@ def create_shape(context, pts, indices, node, key, mtx, mat):
if (pts):
loopstart = []
looplines = loop = 0
nbr_faces = len(indices)
for fid in range(nbr_faces):
nb_faces = len(indices)
for fid in range(nb_faces):
polyface = indices[fid]
looplines += len(polyface)
shape.vertices.add(len(pts) // 3)
shape.loops.add(looplines)
shape.polygons.add(nbr_faces)
shape.polygons.add(nb_faces)
shape.vertices.foreach_set("co", pts)
for vtx in indices:
loopstart.append(loop)
@ -1317,8 +1357,9 @@ def create_shape(context, pts, indices, node, key, mtx, mat):
shape.update()
obj = bpy.data.objects.new(name, shape)
context.view_layer.active_layer_collection.collection.objects.link(obj)
adjust_material(obj, mat)
obj.matrix_world = mtx
if (umt):
adjust_material(obj, mat)
return True
return True
@ -1424,11 +1465,14 @@ def get_poly_data(chunk):
return polylist
def create_editable_poly(context, node, msh, mat, mtx):
coords = point3i = point4i = point6i = pointNi = None
def create_editable_poly(context, node, msh, mat, mtx, umt, uvm):
coords = point4i = point6i = pointNi = None
name = node.get_first(TYP_NAME).data
poly = msh.get_first(0x08FE)
created = False
lidx = []
lcrd = []
lply = []
if (poly):
for child in poly.children:
if (child.types == 0x0100):
@ -1437,20 +1481,34 @@ def create_editable_poly(context, node, msh, mat, mtx):
point6i = child.data
elif (child.types == 0x011A):
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):
vertex = get_poly_4p(point4i)
if (len(vertex) > 0):
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:
created = True
elif (point6i is not None):
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
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
poly = msh.get_first(0x08FE)
created = False
@ -1459,15 +1517,15 @@ def create_editable_mesh(context, node, msh, mat, mtx):
clsid_chunk = poly.get_first(0x0912)
coords = get_point_array(vertex_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
def create_shell(context, node, shell, mat, mtx):
def create_shell(context, node, shell, mat, mtx, umt):
name = node.get_first(TYP_NAME).data
refs = get_references(shell)
msh = refs[-1]
created = create_editable_mesh(context, node, msh, mat, mtx)
created = create_editable_mesh(context, node, msh, mat, mtx, umt)
return created
@ -1477,16 +1535,16 @@ def create_skipable(context, node, skip):
return True
def create_mesh(context, node, msh, mtx, mat):
def create_mesh(context, node, msh, mtx, mat, umt, uvm):
created = False
uid = get_guid(msh)
msh.geometry = None
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):
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}):
created = create_shell(context, node, msh, mat, mtx)
created = create_shell(context, node, msh, mat, mtx, umt)
else:
skip = SKIPPABLE.get(uid)
if (skip is not None):
@ -1494,7 +1552,7 @@ def create_mesh(context, node, msh, mtx, mat):
return created, uid
def create_object(context, node, mscale, transform):
def create_object(context, node, mscale, usemat, uvmesh, transform):
parent = get_node_parent(node)
node.parent = parent
name = get_node_name(node)
@ -1509,26 +1567,26 @@ def create_object(context, node, mscale, transform):
mtx = create_matrix(prs) @ mscale
else:
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:
if (isinstance(chunk, SceneChunk)):
if ((get_guid(chunk) == 0x0001) and (get_super_id(chunk) == 0x0001)):
try:
create_object(context, chunk, mscale, transform)
create_object(context, chunk, mscale, usemat, uvmesh, transform)
except Exception as exc:
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
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)):
maxfile = ImportMaxFile(filename)
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_class_directory(maxfile, filename)
read_video_postqueue(maxfile, filename)
read_scene(context, maxfile, filename, mscale, transform)
read_scene(context, maxfile, filename, mscale, usemat, uvmesh, transform)
else:
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)
if global_matrix is not None:
mscale = global_matrix @ mscale
read(context, filepath, mscale, transform=use_apply_matrix)
mscale = global_matrix @ mscale
read(context, filepath, mscale, usemat=use_material, uvmesh=use_uv_mesh, transform=use_apply_matrix)
return {'FINISHED'}