Export_3ds: Fixed position scaling in animations #104770

Merged
Sebastian Sille merged 3 commits from :main into main 2023-07-23 12:53:10 +02:00
3 changed files with 58 additions and 55 deletions

View File

@ -55,6 +55,11 @@ class Import3DS(bpy.types.Operator, ImportHelper):
soft_min=0.0, soft_max=1000.0, soft_min=0.0, soft_max=1000.0,
default=10.0, default=10.0,
) )
convert_measure: BoolProperty(
name="Convert Measure",
description="Convert from millimeters to meters",
default=False,
)
use_image_search: BoolProperty( use_image_search: BoolProperty(
name="Image Search", name="Image Search",
description="Search subdirectories for any associated images " description="Search subdirectories for any associated images "
@ -112,7 +117,7 @@ class Export3DS(bpy.types.Operator, ExportHelper):
min=0.0, max=100000.0, min=0.0, max=100000.0,
soft_min=0.0, soft_max=100000.0, soft_min=0.0, soft_max=100000.0,
default=1.0, default=1.0,
) )
use_selection: BoolProperty( use_selection: BoolProperty(
name="Selection Only", name="Selection Only",
description="Export selected objects only", description="Export selected objects only",

View File

@ -1086,7 +1086,6 @@ def make_kfdata(revision, start=0, stop=100, curtime=0):
def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size): def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size):
"""Make a chunk for track data. Depending on the ID, this will construct """Make a chunk for track data. Depending on the ID, this will construct
a position, rotation, scale, roll, color, fov, hotspot or falloff track.""" a position, rotation, scale, roll, color, fov, hotspot or falloff track."""
ob_distance = mathutils.Matrix.Diagonal(ob_size)
track_chunk = _3ds_chunk(ID) track_chunk = _3ds_chunk(ID)
if ID in {POS_TRACK_TAG, ROT_TRACK_TAG, SCL_TRACK_TAG, ROLL_TRACK_TAG} and ob.animation_data and ob.animation_data.action: if ID in {POS_TRACK_TAG, ROT_TRACK_TAG, SCL_TRACK_TAG, ROLL_TRACK_TAG} and ob.animation_data and ob.animation_data.action:
@ -1111,7 +1110,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size):
pos_x = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 0), ob_pos.x) pos_x = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 0), ob_pos.x)
pos_y = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 1), ob_pos.y) pos_y = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 1), ob_pos.y)
pos_z = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 2), ob_pos.z) pos_z = next((tc.evaluate(frame) for tc in pos_track if tc.array_index == 2), ob_pos.z)
pos = ob_distance @ mathutils.Vector((pos_x, pos_y, pos_z)) pos = ob_size @ mathutils.Vector((pos_x, pos_y, pos_z))
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("tcb_flags", _3ds_ushort())
track_chunk.add_variable("position", _3ds_point_3d((pos.x, pos.y, pos.z))) track_chunk.add_variable("position", _3ds_point_3d((pos.x, pos.y, pos.z)))
@ -1317,7 +1316,8 @@ def make_object_node(ob, translation, rotation, scale, name_id):
obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.data.auto_smooth_angle, 6))) obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.data.auto_smooth_angle, 6)))
obj_node.add_subchunk(obj_morph_smooth) obj_node.add_subchunk(obj_morph_smooth)
# Add track chunks for color, position, rotation and scale # Add track chunks for position, rotation, size
ob_scale = scale[name] # and collect masterscale
if parent is None or (parent.name not in name_id): if parent is None or (parent.name not in name_id):
ob_pos = translation[name] ob_pos = translation[name]
ob_rot = rotation[name] ob_rot = rotation[name]
@ -1328,7 +1328,7 @@ def make_object_node(ob, translation, rotation, scale, name_id):
ob_rot = rotation[name].to_quaternion().cross(rotation[parent.name].to_quaternion().copy().inverted()).to_euler() ob_rot = rotation[name].to_quaternion().cross(rotation[parent.name].to_quaternion().copy().inverted()).to_euler()
ob_size = mathutils.Vector((1.0, 1.0, 1.0)) ob_size = mathutils.Vector((1.0, 1.0, 1.0))
obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, ob, ob_pos, ob_rot, ob_scale))
if ob.type in {'MESH', 'EMPTY'}: if ob.type in {'MESH', 'EMPTY'}:
obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size)) obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
@ -1375,7 +1375,7 @@ def make_target_node(ob, translation, rotation, scale, name_id):
# Calculate target position # Calculate target position
ob_pos = translation[name] ob_pos = translation[name]
ob_rot = rotation[name] ob_rot = rotation[name]
ob_size = mathutils.Matrix.Diagonal(scale[name]) ob_scale = scale[name]
target_pos = calc_target(ob_pos, ob_rot.x, ob_rot.z) target_pos = calc_target(ob_pos, ob_rot.x, ob_rot.z)
# Add track chunks for target position # Add track chunks for target position
@ -1405,7 +1405,7 @@ def make_target_node(ob, translation, rotation, scale, name_id):
rot_target = [fc for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler'] rot_target = [fc for fc in fcurves if fc is not None and fc.data_path == 'rotation_euler']
rot_x = next((tc.evaluate(frame) for tc in rot_target if tc.array_index == 0), ob_rot.x) rot_x = next((tc.evaluate(frame) for tc in rot_target if tc.array_index == 0), ob_rot.x)
rot_z = next((tc.evaluate(frame) for tc in rot_target if tc.array_index == 2), ob_rot.z) rot_z = next((tc.evaluate(frame) for tc in rot_target if tc.array_index == 2), ob_rot.z)
target_distance = ob_size @ mathutils.Vector((loc_x, loc_y, loc_z)) target_distance = ob_scale @ mathutils.Vector((loc_x, loc_y, loc_z))
target_pos = calc_target(target_distance, rot_x, rot_z) target_pos = calc_target(target_distance, rot_x, rot_z)
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame))) track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
track_chunk.add_variable("tcb_flags", _3ds_ushort()) track_chunk.add_variable("tcb_flags", _3ds_ushort())
@ -1628,29 +1628,29 @@ def save(operator, context, filepath="", scale_factor=1.0, use_selection=False,
name_id = {} name_id = {}
for ob, data, matrix in mesh_objects: for ob, data, matrix in mesh_objects:
translation[ob.name] = mtx_scale @ ob.location.copy() translation[ob.name] = mtx_scale @ ob.location
rotation[ob.name] = ob.rotation_euler.copy() rotation[ob.name] = ob.rotation_euler
scale[ob.name] = ob.scale.copy() scale[ob.name] = mtx_scale.copy()
name_id[ob.name] = len(name_id) name_id[ob.name] = len(name_id)
object_id[ob.name] = len(object_id) object_id[ob.name] = len(object_id)
for ob in empty_objects: for ob in empty_objects:
translation[ob.name] = mtx_scale @ ob.location.copy() translation[ob.name] = mtx_scale @ ob.location
rotation[ob.name] = ob.rotation_euler.copy() rotation[ob.name] = ob.rotation_euler
scale[ob.name] = ob.scale.copy() scale[ob.name] = mtx_scale.copy()
name_id[ob.name] = len(name_id) name_id[ob.name] = len(name_id)
for ob in light_objects: for ob in light_objects:
translation[ob.name] = mtx_scale @ ob.location.copy() translation[ob.name] = mtx_scale @ ob.location
rotation[ob.name] = ob.rotation_euler.copy() rotation[ob.name] = ob.rotation_euler
scale[ob.name] = mtx_scale.copy().to_scale() scale[ob.name] = mtx_scale.copy()
name_id[ob.name] = len(name_id) name_id[ob.name] = len(name_id)
object_id[ob.name] = len(object_id) object_id[ob.name] = len(object_id)
for ob in camera_objects: for ob in camera_objects:
translation[ob.name] = mtx_scale @ ob.location.copy() translation[ob.name] = mtx_scale @ ob.location
rotation[ob.name] = ob.rotation_euler.copy() rotation[ob.name] = ob.rotation_euler
scale[ob.name] = mtx_scale.copy().to_scale() scale[ob.name] = mtx_scale.copy()
name_id[ob.name] = len(name_id) name_id[ob.name] = len(name_id)
object_id[ob.name] = len(object_id) object_id[ob.name] = len(object_id)

View File

@ -325,7 +325,8 @@ def add_texture_to_material(image, contextWrapper, pct, extend, alpha, scale, of
childs_list = [] childs_list = []
parent_list = [] parent_list = []
def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAIN, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE): def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAIN,
IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE, MEASURE):
contextObName = None contextObName = None
contextLamp = None contextLamp = None
@ -362,15 +363,9 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
pivot_list = [] # pivots with hierarchy handling pivot_list = [] # pivots with hierarchy handling
trackposition = {} # keep track to position for target calculation trackposition = {} # keep track to position for target calculation
def putContextMesh( def putContextMesh(context, myContextMesh_vertls, myContextMesh_facels, myContextMesh_flag,
context, myContextMeshMaterials, myContextMesh_smooth, WORLD_MATRIX):
myContextMesh_vertls,
myContextMesh_facels,
myContextMesh_flag,
myContextMeshMaterials,
myContextMesh_smooth,
WORLD_MATRIX,
):
bmesh = bpy.data.meshes.new(contextObName) bmesh = bpy.data.meshes.new(contextObName)
if myContextMesh_facels is None: if myContextMesh_facels is None:
@ -431,8 +426,8 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
imported_objects.append(ob) imported_objects.append(ob)
if myContextMesh_flag: if myContextMesh_flag:
"""Bit 0 (0x1) sets edge CA visible, Bit 1 (0x2) sets edge BC visible and Bit 2 (0x4) sets edge AB visible """Bit 0 (0x1) sets edge CA visible, Bit 1 (0x2) sets edge BC visible and
In Blender we use sharp edges for those flags""" Bit 2 (0x4) sets edge AB visible. In Blender we use sharp edges for those flags."""
for f, pl in enumerate(bmesh.polygons): for f, pl in enumerate(bmesh.polygons):
face = myContextMesh_facels[f] face = myContextMesh_facels[f]
faceflag = myContextMesh_flag[f] faceflag = myContextMesh_flag[f]
@ -541,7 +536,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
0x40 activates alpha source, 0x80 activates tinting, 0x100 ignores alpha, 0x200 activates RGB tint. 0x40 activates alpha source, 0x80 activates tinting, 0x100 ignores alpha, 0x200 activates RGB tint.
Bits 0x80, 0x100, and 0x200 are only used with TEXMAP, TEX2MAP, and SPECMAP chunks. Bits 0x80, 0x100, and 0x200 are only used with TEXMAP, TEX2MAP, and SPECMAP chunks.
0x40, when used with a TEXMAP, TEX2MAP, or SPECMAP chunk must be accompanied with a tint bit, 0x40, when used with a TEXMAP, TEX2MAP, or SPECMAP chunk must be accompanied with a tint bit,
either 0x100 or 0x200, tintcolor will be processed if colorchunks are present""" either 0x100 or 0x200, tintcolor will be processed if colorchunks are present."""
tiling = read_short(temp_chunk) tiling = read_short(temp_chunk)
if tiling & 0x1: if tiling & 0x1:
extend = 'decal' extend = 'decal'
@ -620,7 +615,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
def read_track_data(track_chunk): def read_track_data(track_chunk):
"""Trackflags 0x1, 0x2 and 0x3 are for looping. 0x8, 0x10 and 0x20 """Trackflags 0x1, 0x2 and 0x3 are for looping. 0x8, 0x10 and 0x20
locks the XYZ axes. 0x100, 0x200 and 0x400 unlinks the XYZ axes""" locks the XYZ axes. 0x100, 0x200 and 0x400 unlinks the XYZ axes."""
tflags = read_short(track_chunk) tflags = read_short(track_chunk)
contextTrack_flag = tflags contextTrack_flag = tflags
temp_data = file.read(SZ_U_INT * 2) temp_data = file.read(SZ_U_INT * 2)
@ -687,7 +682,8 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
# is it an object info chunk? # is it an object info chunk?
elif new_chunk.ID == OBJECTINFO: elif new_chunk.ID == OBJECTINFO:
process_next_chunk(context, file, new_chunk, imported_objects, CONSTRAIN, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE) process_next_chunk(context, file, new_chunk, imported_objects, CONSTRAIN,
IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE, MEASURE)
# keep track of how much we read in the main chunk # keep track of how much we read in the main chunk
new_chunk.bytes_read += temp_chunk.bytes_read new_chunk.bytes_read += temp_chunk.bytes_read
@ -1105,6 +1101,8 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
for keydata in keyframe_data.items(): for keydata in keyframe_data.items():
trackposition[keydata[0]] = keydata[1] # Keep track to position for target calculation trackposition[keydata[0]] = keydata[1] # Keep track to position for target calculation
child.location = apply_constrain(keydata[1]) if hierarchy == ROOT_OBJECT else mathutils.Vector(keydata[1]) child.location = apply_constrain(keydata[1]) if hierarchy == ROOT_OBJECT else mathutils.Vector(keydata[1])
if MEASURE:
child.location = child.location * 0.001
if hierarchy == ROOT_OBJECT: if hierarchy == ROOT_OBJECT:
child.location.rotate(CONVERSE) child.location.rotate(CONVERSE)
if not contextTrack_flag & 0x100: # Flag 0x100 unlinks X axis if not contextTrack_flag & 0x100: # Flag 0x100 unlinks X axis
@ -1131,6 +1129,8 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
scale = mathutils.Vector.Fill(3, (CONSTRAIN * 0.1)) if CONSTRAIN != 0.0 else child.scale scale = mathutils.Vector.Fill(3, (CONSTRAIN * 0.1)) if CONSTRAIN != 0.0 else child.scale
transformation = mathutils.Matrix.LocRotScale(locate, rotate, scale) transformation = mathutils.Matrix.LocRotScale(locate, rotate, scale)
child.matrix_world = transformation child.matrix_world = transformation
if MEASURE:
child.matrix_world = mathutils.Matrix.Scale(0.001,4) @ child.matrix_world
if hierarchy == ROOT_OBJECT: if hierarchy == ROOT_OBJECT:
child.matrix_world = CONVERSE @ child.matrix_world child.matrix_world = CONVERSE @ child.matrix_world
child.keyframe_insert(data_path="rotation_euler", index=0, frame=keydata[0]) child.keyframe_insert(data_path="rotation_euler", index=0, frame=keydata[0])
@ -1305,7 +1305,8 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
# IMPORT # # IMPORT #
########## ##########
def load_3ds(filepath, context, CONSTRAIN=10.0, IMAGE_SEARCH=True, WORLD_MATRIX=False, KEYFRAME=True, APPLY_MATRIX=True, CONVERSE=None): def load_3ds(filepath, context, CONSTRAIN=10.0, MEASURE=False, IMAGE_SEARCH=True,
WORLD_MATRIX=False, KEYFRAME=True, APPLY_MATRIX=True, CONVERSE=None):
print("importing 3DS: %r..." % (filepath), end="") print("importing 3DS: %r..." % (filepath), end="")
@ -1335,7 +1336,8 @@ def load_3ds(filepath, context, CONSTRAIN=10.0, IMAGE_SEARCH=True, WORLD_MATRIX=
scn = context.scene scn = context.scene
imported_objects = [] # Fill this list with objects imported_objects = [] # Fill this list with objects
process_next_chunk(context, file, current_chunk, imported_objects, CONSTRAIN, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE) process_next_chunk(context, file, current_chunk, imported_objects, CONSTRAIN,
IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, CONVERSE, MEASURE)
# fixme, make unglobal # fixme, make unglobal
object_dictionary.clear() object_dictionary.clear()
@ -1344,8 +1346,13 @@ def load_3ds(filepath, context, CONSTRAIN=10.0, IMAGE_SEARCH=True, WORLD_MATRIX=
if APPLY_MATRIX: if APPLY_MATRIX:
for ob in imported_objects: for ob in imported_objects:
if ob.type == 'MESH': if ob.type == 'MESH':
me = ob.data ob.data.transform(ob.matrix_local.inverted())
me.transform(ob.matrix_local.inverted())
if MEASURE:
unit_mtx = mathutils.Matrix.Scale(0.001,4)
for ob in imported_objects:
if ob.type == 'MESH':
ob.data.transform(unit_mtx)
if CONVERSE and not KEYFRAME: if CONVERSE and not KEYFRAME:
for ob in imported_objects: for ob in imported_objects:
@ -1424,25 +1431,16 @@ def load_3ds(filepath, context, CONSTRAIN=10.0, IMAGE_SEARCH=True, WORLD_MATRIX=
file.close() file.close()
def load(operator, def load(operator, context, filepath="", constrain_size=0.0,
context, convert_measure=False, use_image_search=True,
filepath="", use_world_matrix=False, read_keyframe=True,
constrain_size=0.0, use_apply_transform=True, global_matrix=None,
use_image_search=True,
use_world_matrix=False,
read_keyframe=True,
use_apply_transform=True,
global_matrix=None,
): ):
load_3ds(filepath, load_3ds(filepath, context, CONSTRAIN=constrain_size,
context, MEASURE=convert_measure, IMAGE_SEARCH=use_image_search,
CONSTRAIN=constrain_size, WORLD_MATRIX=use_world_matrix, KEYFRAME=read_keyframe,
IMAGE_SEARCH=use_image_search, APPLY_MATRIX=use_apply_transform, CONVERSE=global_matrix,
WORLD_MATRIX=use_world_matrix,
KEYFRAME=read_keyframe,
APPLY_MATRIX=use_apply_transform,
CONVERSE=global_matrix,
) )
return {'FINISHED'} return {'FINISHED'}