Pose Library: Update to use the asset shelf (when enabled) #104546
@ -700,7 +700,7 @@ class torus_knot_plus(Operator, AddObjectHelper):
|
||||
print("Approximate average TK length = %.2f" % avgTKLen)
|
||||
|
||||
# x N factor = control points per unit length
|
||||
self.torus_res = max(3, avgTKLen / links * 8)
|
||||
self.torus_res = max(3, int(avgTKLen / links) * 8)
|
||||
|
||||
# update align matrix
|
||||
self.align_matrix = align_matrix(self, context)
|
||||
|
@ -18,7 +18,7 @@ import bpy
|
||||
bl_info = {
|
||||
"name": "Autodesk 3DS format",
|
||||
"author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand",
|
||||
"version": (2, 4, 3),
|
||||
"version": (2, 4, 6),
|
||||
"blender": (3, 6, 0),
|
||||
"location": "File > Import-Export",
|
||||
"description": "3DS Import/Export meshes, UVs, materials, textures, "
|
||||
@ -40,35 +40,51 @@ if "bpy" in locals():
|
||||
@orientation_helper(axis_forward='Y', axis_up='Z')
|
||||
class Import3DS(bpy.types.Operator, ImportHelper):
|
||||
"""Import from 3DS file format (.3ds)"""
|
||||
bl_idname = "import_scene.autodesk_3ds"
|
||||
bl_idname = "import_scene.max3ds"
|
||||
bl_label = 'Import 3DS'
|
||||
bl_options = {'UNDO'}
|
||||
bl_options = {'PRESET', 'UNDO'}
|
||||
|
||||
filename_ext = ".3ds"
|
||||
filter_glob: StringProperty(default="*.3ds", options={'HIDDEN'})
|
||||
|
||||
constrain_size: FloatProperty(
|
||||
name="Size Constraint",
|
||||
name="Constrain Size",
|
||||
description="Scale the model by 10 until it reaches the "
|
||||
"size constraint (0 to disable)",
|
||||
min=0.0, max=1000.0,
|
||||
soft_min=0.0, soft_max=1000.0,
|
||||
default=10.0,
|
||||
)
|
||||
use_scene_unit: BoolProperty(
|
||||
name="Scene Units",
|
||||
description="Converts to scene unit length settings",
|
||||
default=False,
|
||||
)
|
||||
use_image_search: BoolProperty(
|
||||
name="Image Search",
|
||||
description="Search subdirectories for any associated images "
|
||||
"(Warning, may be slow)",
|
||||
default=True,
|
||||
)
|
||||
object_filter: EnumProperty(
|
||||
name="Object Filter", options={'ENUM_FLAG'},
|
||||
items=(('WORLD', "World".rjust(11), "", 'WORLD_DATA', 0x1),
|
||||
('MESH', "Mesh".rjust(11), "", 'MESH_DATA', 0x2),
|
||||
('LIGHT', "Light".rjust(12), "", 'LIGHT_DATA', 0x4),
|
||||
('CAMERA', "Camera".rjust(11), "", 'CAMERA_DATA', 0x8),
|
||||
('EMPTY', "Empty".rjust(11), "", 'EMPTY_AXIS', 0x10),
|
||||
),
|
||||
description="Object types to import",
|
||||
default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY'},
|
||||
)
|
||||
use_apply_transform: BoolProperty(
|
||||
name="Apply Transform",
|
||||
description="Workaround for object transformations "
|
||||
"importing incorrectly",
|
||||
default=True,
|
||||
)
|
||||
read_keyframe: BoolProperty(
|
||||
name="Read Keyframe",
|
||||
use_keyframes: BoolProperty(
|
||||
name="Animation",
|
||||
description="Read the keyframe data",
|
||||
default=True,
|
||||
)
|
||||
@ -77,6 +93,11 @@ class Import3DS(bpy.types.Operator, ImportHelper):
|
||||
description="Transform to matrix world",
|
||||
default=False,
|
||||
)
|
||||
use_cursor: BoolProperty(
|
||||
name="Cursor Origin",
|
||||
description="Read the 3D cursor location",
|
||||
default=False,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
from . import import_3ds
|
||||
@ -93,12 +114,84 @@ class Import3DS(bpy.types.Operator, ImportHelper):
|
||||
|
||||
return import_3ds.load(self, context, **keywords)
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
class MAX3DS_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_SCENE_OT_max3ds"
|
||||
|
||||
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_image_search")
|
||||
layrow.label(text="", icon='OUTLINER_OB_IMAGE' if operator.use_image_search else 'IMAGE_DATA')
|
||||
layout.column().prop(operator, "object_filter")
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_keyframes")
|
||||
layrow.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER')
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_cursor")
|
||||
layrow.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR')
|
||||
|
||||
|
||||
class MAX3DS_PT_import_transform(bpy.types.Panel):
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
bl_region_type = 'TOOL_PROPS'
|
||||
bl_label = "Transform"
|
||||
bl_parent_id = "FILE_PT_operator"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
sfile = context.space_data
|
||||
operator = sfile.active_operator
|
||||
|
||||
return operator.bl_idname == "IMPORT_SCENE_OT_max3ds"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
sfile = context.space_data
|
||||
operator = sfile.active_operator
|
||||
|
||||
layout.prop(operator, "constrain_size")
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_scene_unit")
|
||||
layrow.label(text="", icon='EMPTY_ARROWS' if operator.use_scene_unit else 'EMPTY_DATA')
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_apply_transform")
|
||||
layrow.label(text="", icon='MESH_CUBE' if operator.use_apply_transform else 'MOD_SOLIDIFY')
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_world_matrix")
|
||||
layrow.label(text="", icon='WORLD' if operator.use_world_matrix else 'META_BALL')
|
||||
layout.prop(operator, "axis_forward")
|
||||
layout.prop(operator, "axis_up")
|
||||
|
||||
|
||||
@orientation_helper(axis_forward='Y', axis_up='Z')
|
||||
class Export3DS(bpy.types.Operator, ExportHelper):
|
||||
"""Export to 3DS file format (.3ds)"""
|
||||
bl_idname = "export_scene.autodesk_3ds"
|
||||
bl_idname = "export_scene.max3ds"
|
||||
bl_label = 'Export 3DS'
|
||||
bl_options = {'PRESET', 'UNDO'}
|
||||
|
||||
filename_ext = ".3ds"
|
||||
filter_glob: StringProperty(
|
||||
@ -106,21 +199,49 @@ class Export3DS(bpy.types.Operator, ExportHelper):
|
||||
options={'HIDDEN'},
|
||||
)
|
||||
|
||||
scale_factor: FloatProperty(
|
||||
name="Scale Factor",
|
||||
description="Master scale factor for all objects",
|
||||
min=0.0, max=100000.0,
|
||||
soft_min=0.0, soft_max=100000.0,
|
||||
default=1.0,
|
||||
)
|
||||
use_scene_unit: BoolProperty(
|
||||
name="Scene Units",
|
||||
description="Take the scene unit length settings into account",
|
||||
default=False,
|
||||
)
|
||||
use_selection: BoolProperty(
|
||||
name="Selection Only",
|
||||
name="Selection",
|
||||
description="Export selected objects only",
|
||||
default=False,
|
||||
)
|
||||
use_hierarchy: bpy.props.BoolProperty(
|
||||
name="Export Hierarchy",
|
||||
object_filter: EnumProperty(
|
||||
name="Object Filter", options={'ENUM_FLAG'},
|
||||
items=(('WORLD', "World".rjust(11), "", 'WORLD_DATA',0x1),
|
||||
('MESH', "Mesh".rjust(11), "", 'MESH_DATA', 0x2),
|
||||
('LIGHT', "Light".rjust(12), "", 'LIGHT_DATA',0x4),
|
||||
('CAMERA', "Camera".rjust(11), "", 'CAMERA_DATA',0x8),
|
||||
('EMPTY', "Empty".rjust(11), "", 'EMPTY_AXIS',0x10),
|
||||
),
|
||||
description="Object types to export",
|
||||
default={'WORLD', 'MESH', 'LIGHT', 'CAMERA', 'EMPTY'},
|
||||
)
|
||||
use_hierarchy: BoolProperty(
|
||||
name="Hierarchy",
|
||||
description="Export hierarchy chunks",
|
||||
default=False,
|
||||
)
|
||||
write_keyframe: BoolProperty(
|
||||
name="Write Keyframe",
|
||||
use_keyframes: BoolProperty(
|
||||
name="Animation",
|
||||
description="Write the keyframe data",
|
||||
default=False,
|
||||
)
|
||||
use_cursor: BoolProperty(
|
||||
name="Cursor Origin",
|
||||
description="Save the 3D cursor location",
|
||||
default=False,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
from . import export_3ds
|
||||
@ -137,6 +258,74 @@ class Export3DS(bpy.types.Operator, ExportHelper):
|
||||
|
||||
return export_3ds.save(self, context, **keywords)
|
||||
|
||||
def draw(self, context):
|
||||
pass
|
||||
|
||||
|
||||
class MAX3DS_PT_export_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 == "EXPORT_SCENE_OT_max3ds"
|
||||
|
||||
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_selection")
|
||||
layrow.label(text="", icon='RESTRICT_SELECT_OFF' if operator.use_selection else 'RESTRICT_SELECT_ON')
|
||||
layout.column().prop(operator, "object_filter")
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_hierarchy")
|
||||
layrow.label(text="", icon='OUTLINER' if operator.use_hierarchy else 'CON_CHILDOF')
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_keyframes")
|
||||
layrow.label(text="", icon='ANIM' if operator.use_keyframes else 'DECORATE_DRIVER')
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_cursor")
|
||||
layrow.label(text="", icon='PIVOT_CURSOR' if operator.use_cursor else 'CURSOR')
|
||||
|
||||
|
||||
class MAX3DS_PT_export_transform(bpy.types.Panel):
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
bl_region_type = 'TOOL_PROPS'
|
||||
bl_label = "Transform"
|
||||
bl_parent_id = "FILE_PT_operator"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
sfile = context.space_data
|
||||
operator = sfile.active_operator
|
||||
|
||||
return operator.bl_idname == "EXPORT_SCENE_OT_max3ds"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
sfile = context.space_data
|
||||
operator = sfile.active_operator
|
||||
|
||||
layout.prop(operator, "scale_factor")
|
||||
layrow = layout.row(align=True)
|
||||
layrow.prop(operator, "use_scene_unit")
|
||||
layrow.label(text="", icon='EMPTY_ARROWS' if operator.use_scene_unit else 'EMPTY_DATA')
|
||||
layout.prop(operator, "axis_forward")
|
||||
layout.prop(operator, "axis_up")
|
||||
|
||||
|
||||
# Add to a menu
|
||||
def menu_func_export(self, context):
|
||||
@ -149,16 +338,22 @@ def menu_func_import(self, context):
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(Import3DS)
|
||||
bpy.utils.register_class(MAX3DS_PT_import_include)
|
||||
bpy.utils.register_class(MAX3DS_PT_import_transform)
|
||||
bpy.utils.register_class(Export3DS)
|
||||
|
||||
bpy.utils.register_class(MAX3DS_PT_export_include)
|
||||
bpy.utils.register_class(MAX3DS_PT_export_transform)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(Import3DS)
|
||||
bpy.utils.unregister_class(MAX3DS_PT_import_include)
|
||||
bpy.utils.unregister_class(MAX3DS_PT_import_transform)
|
||||
bpy.utils.unregister_class(Export3DS)
|
||||
|
||||
bpy.utils.unregister_class(MAX3DS_PT_export_include)
|
||||
bpy.utils.unregister_class(MAX3DS_PT_export_transform)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
|
||||
|
@ -24,13 +24,24 @@ from bpy_extras import node_shader_utils
|
||||
PRIMARY = 0x4D4D
|
||||
|
||||
# >----- Main Chunks
|
||||
OBJECTINFO = 0x3D3D # Main mesh object chunk before material and object information
|
||||
MESHVERSION = 0x3D3E # This gives the version of the mesh
|
||||
VERSION = 0x0002 # This gives the version of the .3ds file
|
||||
KFDATA = 0xB000 # This is the header for all of the keyframe info
|
||||
|
||||
# >----- sub defines of OBJECTINFO
|
||||
OBJECTINFO = 0x3D3D # Main mesh object chunk before material and object information
|
||||
MESHVERSION = 0x3D3E # This gives the version of the mesh
|
||||
BITMAP = 0x1100 # The background image name
|
||||
USE_BITMAP = 0x1101 # The background image flag
|
||||
SOLIDBACKGND = 0x1200 # The background color (RGB)
|
||||
USE_SOLIDBGND = 0x1201 # The background color flag
|
||||
VGRADIENT = 0x1300 # The background gradient colors
|
||||
USE_VGRADIENT = 0x1301 # The background gradient flag
|
||||
O_CONSTS = 0x1500 # The origin of the 3D cursor
|
||||
AMBIENTLIGHT = 0x2100 # The color of the ambient light
|
||||
FOG = 0x2200 # The fog atmosphere settings
|
||||
USE_FOG = 0x2201 # The fog atmosphere flag
|
||||
LAYER_FOG = 0x2302 # The fog layer atmosphere settings
|
||||
USE_LAYER_FOG = 0x2303 # The fog layer atmosphere flag
|
||||
MATERIAL = 45055 # 0xAFFF // This stored the texture info
|
||||
OBJECT = 16384 # 0x4000 // This stores the faces, vertices, etc...
|
||||
|
||||
@ -563,6 +574,7 @@ def make_material_texture_chunk(chunk_id, texslots, pct):
|
||||
|
||||
def add_texslot(texslot):
|
||||
image = texslot.image
|
||||
socket = None
|
||||
|
||||
filename = bpy.path.basename(image.filepath)
|
||||
mat_sub_file = _3ds_chunk(MAT_MAP_FILE)
|
||||
@ -659,7 +671,7 @@ def make_material_chunk(material, image):
|
||||
material_chunk.add_subchunk(make_material_subchunk(MATDIFFUSE, (0.8, 0.8, 0.8)))
|
||||
material_chunk.add_subchunk(make_material_subchunk(MATSPECULAR, (1.0, 1.0, 1.0)))
|
||||
material_chunk.add_subchunk(make_percent_subchunk(MATSHINESS, 0.8))
|
||||
material_chunk.add_subchunk(make_percent_subchunk(MATSHIN2, 1))
|
||||
material_chunk.add_subchunk(make_percent_subchunk(MATSHIN2, 0.5))
|
||||
material_chunk.add_subchunk(shading)
|
||||
|
||||
elif material and material.use_nodes:
|
||||
@ -1045,6 +1057,19 @@ def make_mesh_chunk(ob, mesh, matrix, materialDict, translation):
|
||||
return mesh_chunk
|
||||
|
||||
|
||||
def calc_target(posi, tilt=0.0, pan=0.0):
|
||||
"""Calculate target position for cameras and spotlights."""
|
||||
adjacent = math.radians(90)
|
||||
turn = 0.0 if abs(pan) < adjacent else -0.0
|
||||
lean = 0.0 if abs(tilt) > adjacent else -0.0
|
||||
diagonal = math.sqrt(pow(posi.x ,2) + pow(posi.y ,2))
|
||||
target_x = math.copysign(posi.x + (posi.y * math.tan(pan)), pan)
|
||||
target_y = math.copysign(posi.y + (posi.x * math.tan(adjacent - pan)), turn)
|
||||
target_z = math.copysign(posi.z + diagonal * math.tan(adjacent - tilt), lean)
|
||||
|
||||
return target_x, target_y, target_z
|
||||
|
||||
|
||||
#################
|
||||
# KEYFRAME DATA #
|
||||
#################
|
||||
@ -1070,6 +1095,7 @@ def make_kfdata(revision, start=0, stop=100, curtime=0):
|
||||
kfdata.add_subchunk(kfcurtime)
|
||||
return kfdata
|
||||
|
||||
|
||||
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
|
||||
a position, rotation, scale, roll, color, fov, hotspot or falloff track."""
|
||||
@ -1097,9 +1123,10 @@ 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_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 = 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_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)))
|
||||
|
||||
elif ID == ROT_TRACK_TAG: # Rotation
|
||||
for i, frame in enumerate(kframes):
|
||||
@ -1110,14 +1137,14 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size):
|
||||
quat = mathutils.Euler((rot_x, rot_y, rot_z)).to_quaternion().inverted()
|
||||
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
||||
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
||||
track_chunk.add_variable("rotation", _3ds_point_4d((quat.angle, quat.axis[0], quat.axis[1], quat.axis[2])))
|
||||
track_chunk.add_variable("rotation", _3ds_point_4d((quat.angle, quat.axis.x, quat.axis.y, quat.axis.z)))
|
||||
|
||||
elif ID == SCL_TRACK_TAG: # Scale
|
||||
for i, frame in enumerate(kframes):
|
||||
scale_track = [fc for fc in fcurves if fc is not None and fc.data_path == 'scale']
|
||||
size_x = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 0), ob_size[0])
|
||||
size_y = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 1), ob_size[1])
|
||||
size_z = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 2), ob_size[2])
|
||||
size_x = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 0), ob_size.x)
|
||||
size_y = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 1), ob_size.y)
|
||||
size_z = next((tc.evaluate(frame) for tc in scale_track if tc.array_index == 2), ob_size.z)
|
||||
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
||||
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
||||
track_chunk.add_variable("scale", _3ds_point_3d((size_x, size_y, size_z)))
|
||||
@ -1194,7 +1221,7 @@ def make_track_chunk(ID, ob, ob_pos, ob_rot, ob_size):
|
||||
|
||||
elif ID == ROT_TRACK_TAG: # Rotation (angle first [radians], followed by axis)
|
||||
quat = ob_rot.to_quaternion().inverted()
|
||||
track_chunk.add_variable("rotation", _3ds_point_4d((quat.angle, quat.axis[0], quat.axis[1], quat.axis[2])))
|
||||
track_chunk.add_variable("rotation", _3ds_point_4d((quat.angle, quat.axis.x, quat.axis.y, quat.axis.z)))
|
||||
|
||||
elif ID == SCL_TRACK_TAG: # Scale vector
|
||||
track_chunk.add_variable("scale", _3ds_point_3d(ob_size))
|
||||
@ -1302,18 +1329,19 @@ 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_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):
|
||||
ob_pos = translation[name]
|
||||
ob_rot = rotation[name]
|
||||
ob_size = scale[name]
|
||||
ob_size = ob.scale
|
||||
|
||||
else: # Calculate child position and rotation of the object center, no scale applied
|
||||
ob_pos = translation[name] - translation[parent.name]
|
||||
ob_rot = rotation[name].to_quaternion().cross(rotation[parent.name].to_quaternion().copy().inverted()).to_euler()
|
||||
ob_size = (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'}:
|
||||
obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, ob, ob_pos, ob_rot, ob_size))
|
||||
@ -1360,12 +1388,8 @@ def make_target_node(ob, translation, rotation, scale, name_id):
|
||||
# Calculate target position
|
||||
ob_pos = translation[name]
|
||||
ob_rot = rotation[name]
|
||||
ob_size = scale[name]
|
||||
|
||||
diagonal = math.copysign(math.sqrt(pow(ob_pos.x, 2) + pow(ob_pos.y, 2)), ob_pos.y)
|
||||
target_x = -1 * math.copysign(ob_pos.x + (ob_pos.y * math.tan(ob_rot.z)), ob_rot.x)
|
||||
target_y = -1 * math.copysign(ob_pos.y + (ob_pos.x * math.tan(math.radians(90) - ob_rot.z)), ob_rot.z)
|
||||
target_z = -1 * math.copysign(diagonal * math.tan(math.radians(90) - ob_rot.x), ob_pos.z)
|
||||
ob_scale = scale[name]
|
||||
target_pos = calc_target(ob_pos, ob_rot.x, ob_rot.z)
|
||||
|
||||
# Add track chunks for target position
|
||||
track_chunk = _3ds_chunk(POS_TRACK_TAG)
|
||||
@ -1394,13 +1418,11 @@ 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_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)
|
||||
diagonal = math.copysign(math.sqrt(pow(loc_x, 2) + pow(loc_y, 2)), loc_y)
|
||||
target_x = -1 * math.copysign(loc_x + (loc_y * math.tan(rot_z)), rot_x)
|
||||
target_y = -1 * math.copysign(loc_y + (loc_x * math.tan(math.radians(90) - rot_z)), rot_z)
|
||||
target_z = -1 * math.copysign(diagonal * math.tan(math.radians(90) - rot_x), loc_z)
|
||||
target_distance = ob_scale @ mathutils.Vector((loc_x, loc_y, loc_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_flags", _3ds_ushort())
|
||||
track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z)))
|
||||
track_chunk.add_variable("position", _3ds_point_3d(target_pos))
|
||||
|
||||
else: # Track header
|
||||
track_chunk.add_variable("track_flags", _3ds_ushort(0x40)) # Based on observation default flag is 0x40
|
||||
@ -1410,7 +1432,7 @@ def make_target_node(ob, translation, rotation, scale, name_id):
|
||||
# Keyframe header
|
||||
track_chunk.add_variable("tcb_frame", _3ds_uint(0))
|
||||
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
||||
track_chunk.add_variable("position", _3ds_point_3d((target_x, target_y, target_z)))
|
||||
track_chunk.add_variable("position", _3ds_point_3d(target_pos))
|
||||
|
||||
tar_node.add_subchunk(track_chunk)
|
||||
|
||||
@ -1437,7 +1459,38 @@ def make_ambient_node(world):
|
||||
amb_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT))
|
||||
amb_node.add_subchunk(amb_node_header_chunk)
|
||||
|
||||
if world.animation_data.action:
|
||||
if world.use_nodes and world.node_tree.animation_data.action:
|
||||
ambioutput = 'EMISSION' ,'MIX_SHADER', 'WORLD_OUTPUT'
|
||||
action = world.node_tree.animation_data.action
|
||||
links = world.node_tree.links
|
||||
ambilinks = [lk for lk in links if lk.from_node.type in {'EMISSION', 'RGB'} and lk.to_node.type in ambioutput]
|
||||
if ambilinks and action.fcurves:
|
||||
fcurves = action.fcurves
|
||||
fcurves.update()
|
||||
emission = next((lk.from_socket.node for lk in ambilinks if lk.to_node.type in ambioutput), False)
|
||||
ambinode = next((lk.from_socket.node for lk in ambilinks if lk.to_node.type == 'EMISSION'), emission)
|
||||
kframes = [kf.co[0] for kf in [fc for fc in fcurves if fc is not None][0].keyframe_points]
|
||||
ambipath = ('nodes[\"RGB\"].outputs[0].default_value' if ambinode and ambinode.type == 'RGB' else
|
||||
'nodes[\"Emission\"].inputs[0].default_value')
|
||||
nkeys = len(kframes)
|
||||
if not 0 in kframes:
|
||||
kframes.append(0)
|
||||
nkeys = nkeys + 1
|
||||
kframes = sorted(set(kframes))
|
||||
track_chunk.add_variable("track_flags", _3ds_ushort(0x40))
|
||||
track_chunk.add_variable("frame_start", _3ds_uint(int(action.frame_start)))
|
||||
track_chunk.add_variable("frame_total", _3ds_uint(int(action.frame_end)))
|
||||
track_chunk.add_variable("nkeys", _3ds_uint(nkeys))
|
||||
|
||||
for i, frame in enumerate(kframes):
|
||||
ambient = [fc.evaluate(frame) for fc in fcurves if fc is not None and fc.data_path == ambipath]
|
||||
if not ambient:
|
||||
ambient = amb_color
|
||||
track_chunk.add_variable("tcb_frame", _3ds_uint(int(frame)))
|
||||
track_chunk.add_variable("tcb_flags", _3ds_ushort())
|
||||
track_chunk.add_variable("color", _3ds_float_color(ambient[:3]))
|
||||
|
||||
elif world.animation_data.action:
|
||||
action = world.animation_data.action
|
||||
if action.fcurves:
|
||||
fcurves = action.fcurves
|
||||
@ -1480,17 +1533,41 @@ def make_ambient_node(world):
|
||||
# EXPORT #
|
||||
##########
|
||||
|
||||
def save(operator, context, filepath="", use_selection=False, use_hierarchy=False, write_keyframe=False, global_matrix=None):
|
||||
|
||||
def save(operator, context, filepath="", scale_factor=1.0, use_scene_unit=False, use_selection=False,
|
||||
object_filter=None, use_hierarchy=False, use_keyframes=False, global_matrix=None, use_cursor=False):
|
||||
"""Save the Blender scene to a 3ds file."""
|
||||
|
||||
# Time the export
|
||||
duration = time.time()
|
||||
context.window.cursor_set('WAIT')
|
||||
|
||||
scene = context.scene
|
||||
layer = context.view_layer
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
world = scene.world
|
||||
|
||||
unit_measure = 1.0
|
||||
if use_scene_unit:
|
||||
unit_length = scene.unit_settings.length_unit
|
||||
if unit_length == 'MILES':
|
||||
unit_measure = 0.000621371
|
||||
elif unit_length == 'KILOMETERS':
|
||||
unit_measure = 0.001
|
||||
elif unit_length == 'FEET':
|
||||
unit_measure = 3.280839895
|
||||
elif unit_length == 'INCHES':
|
||||
unit_measure = 39.37007874
|
||||
elif unit_length == 'CENTIMETERS':
|
||||
unit_measure = 100
|
||||
elif unit_length == 'MILLIMETERS':
|
||||
unit_measure = 1000
|
||||
elif unit_length == 'THOU':
|
||||
unit_measure = 39370.07874
|
||||
elif unit_length == 'MICROMETERS':
|
||||
unit_measure = 1000000
|
||||
|
||||
mtx_scale = mathutils.Matrix.Scale((scale_factor * unit_measure),4)
|
||||
|
||||
if global_matrix is None:
|
||||
global_matrix = mathutils.Matrix()
|
||||
|
||||
@ -1516,8 +1593,14 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
mscale.add_variable("scale", _3ds_float(1.0))
|
||||
object_info.add_subchunk(mscale)
|
||||
|
||||
# Add 3D cursor location
|
||||
if use_cursor:
|
||||
cursor_chunk = _3ds_chunk(O_CONSTS)
|
||||
cursor_chunk.add_variable("cursor", _3ds_point_3d(scene.cursor.location))
|
||||
object_info.add_subchunk(cursor_chunk)
|
||||
|
||||
# Init main keyframe data chunk
|
||||
if write_keyframe:
|
||||
if use_keyframes:
|
||||
revision = 0x0005
|
||||
stop = scene.frame_end
|
||||
start = scene.frame_start
|
||||
@ -1525,13 +1608,89 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
kfdata = make_kfdata(revision, start, stop, curtime)
|
||||
|
||||
# Add AMBIENT color
|
||||
if world is not None:
|
||||
if world is not None and 'WORLD' in object_filter:
|
||||
ambient_chunk = _3ds_chunk(AMBIENTLIGHT)
|
||||
ambient_light = _3ds_chunk(RGB)
|
||||
ambient_light.add_variable("ambient", _3ds_float_color(world.color))
|
||||
ambient_chunk.add_subchunk(ambient_light)
|
||||
object_info.add_subchunk(ambient_chunk)
|
||||
if write_keyframe and world.animation_data:
|
||||
|
||||
# Add BACKGROUND and BITMAP
|
||||
if world.use_nodes:
|
||||
bgtype = 'BACKGROUND'
|
||||
ntree = world.node_tree.links
|
||||
background_color_chunk = _3ds_chunk(RGB)
|
||||
background_chunk = _3ds_chunk(SOLIDBACKGND)
|
||||
background_flag = _3ds_chunk(USE_SOLIDBGND)
|
||||
bgmixer = 'BACKGROUND', 'MIX', 'MIX_RGB'
|
||||
bgshade = 'ADD_SHADER', 'MIX_SHADER', 'OUTPUT_WORLD'
|
||||
bg_tex = 'TEX_IMAGE', 'TEX_ENVIRONMENT'
|
||||
bg_color = next((lk.from_node.inputs[0].default_value[:3] for lk in ntree if lk.from_node.type == bgtype and lk.to_node.type in bgshade), world.color)
|
||||
bg_mixer = next((lk.from_node.type for lk in ntree if lk.from_node.type in bgmixer and lk.to_node.type == bgtype), bgtype)
|
||||
bg_image = next((lk.from_node.image.name for lk in ntree if lk.from_node.type in bg_tex and lk.to_node.type == bg_mixer), False)
|
||||
gradient = next((lk.from_node.color_ramp.elements for lk in ntree if lk.from_node.type == 'VALTORGB' and lk.to_node.type in bgmixer), False)
|
||||
background_color_chunk.add_variable("color", _3ds_float_color(bg_color))
|
||||
background_chunk.add_subchunk(background_color_chunk)
|
||||
if bg_image:
|
||||
background_image = _3ds_chunk(BITMAP)
|
||||
background_flag = _3ds_chunk(USE_BITMAP)
|
||||
background_image.add_variable("image", _3ds_string(sane_name(bg_image)))
|
||||
object_info.add_subchunk(background_image)
|
||||
object_info.add_subchunk(background_chunk)
|
||||
|
||||
# Add VGRADIENT chunk
|
||||
if gradient and len(gradient) >= 3:
|
||||
gradient_chunk = _3ds_chunk(VGRADIENT)
|
||||
background_flag = _3ds_chunk(USE_VGRADIENT)
|
||||
gradient_chunk.add_variable("midpoint", _3ds_float(gradient[1].position))
|
||||
gradient_topcolor_chunk = _3ds_chunk(RGB)
|
||||
gradient_topcolor_chunk.add_variable("color", _3ds_float_color(gradient[2].color[:3]))
|
||||
gradient_chunk.add_subchunk(gradient_topcolor_chunk)
|
||||
gradient_midcolor_chunk = _3ds_chunk(RGB)
|
||||
gradient_midcolor_chunk.add_variable("color", _3ds_float_color(gradient[1].color[:3]))
|
||||
gradient_chunk.add_subchunk(gradient_midcolor_chunk)
|
||||
gradient_lowcolor_chunk = _3ds_chunk(RGB)
|
||||
gradient_lowcolor_chunk.add_variable("color", _3ds_float_color(gradient[0].color[:3]))
|
||||
gradient_chunk.add_subchunk(gradient_lowcolor_chunk)
|
||||
object_info.add_subchunk(gradient_chunk)
|
||||
object_info.add_subchunk(background_flag)
|
||||
|
||||
# Add FOG
|
||||
fognode = next((lk.from_socket.node for lk in ntree if lk.from_socket.node.type == 'VOLUME_ABSORPTION' and lk.to_socket.node.type in bgshade), False)
|
||||
if fognode:
|
||||
fog_chunk = _3ds_chunk(FOG)
|
||||
fog_color_chunk = _3ds_chunk(RGB)
|
||||
use_fog_flag = _3ds_chunk(USE_FOG)
|
||||
fog_density = fognode.inputs['Density'].default_value * 100
|
||||
fog_color_chunk.add_variable("color", _3ds_float_color(fognode.inputs[0].default_value[:3]))
|
||||
fog_chunk.add_variable("nearplane", _3ds_float(world.mist_settings.start))
|
||||
fog_chunk.add_variable("nearfog", _3ds_float(fog_density * 0.5))
|
||||
fog_chunk.add_variable("farplane", _3ds_float(world.mist_settings.depth))
|
||||
fog_chunk.add_variable("farfog", _3ds_float(fog_density + fog_density * 0.5))
|
||||
fog_chunk.add_subchunk(fog_color_chunk)
|
||||
object_info.add_subchunk(fog_chunk)
|
||||
|
||||
# Add LAYER FOG
|
||||
foglayer = next((lk.from_socket.node for lk in ntree if lk.from_socket.node.type == 'VOLUME_SCATTER' and lk.to_socket.node.type in bgshade), False)
|
||||
if foglayer:
|
||||
layerfog_flag = 0
|
||||
if world.mist_settings.falloff == 'QUADRATIC':
|
||||
layerfog_flag |= 0x1
|
||||
if world.mist_settings.falloff == 'INVERSE_QUADRATIC':
|
||||
layerfog_flag |= 0x2
|
||||
layerfog_chunk = _3ds_chunk(LAYER_FOG)
|
||||
layerfog_color_chunk = _3ds_chunk(RGB)
|
||||
use_fog_flag = _3ds_chunk(USE_LAYER_FOG)
|
||||
layerfog_color_chunk.add_variable("color", _3ds_float_color(foglayer.inputs[0].default_value[:3]))
|
||||
layerfog_chunk.add_variable("lowZ", _3ds_float(world.mist_settings.start))
|
||||
layerfog_chunk.add_variable("highZ", _3ds_float(world.mist_settings.height))
|
||||
layerfog_chunk.add_variable("density", _3ds_float(foglayer.inputs[1].default_value))
|
||||
layerfog_chunk.add_variable("flags", _3ds_uint(layerfog_flag))
|
||||
layerfog_chunk.add_subchunk(layerfog_color_chunk)
|
||||
object_info.add_subchunk(layerfog_chunk)
|
||||
if fognode or foglayer and layer.use_pass_mist:
|
||||
object_info.add_subchunk(use_fog_flag)
|
||||
if use_keyframes and world.animation_data:
|
||||
kfdata.add_subchunk(make_ambient_node(world))
|
||||
|
||||
# Make a list of all materials used in the selected meshes (use dictionary, each material is added once)
|
||||
@ -1539,9 +1698,9 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
mesh_objects = []
|
||||
|
||||
if use_selection:
|
||||
objects = [ob for ob in scene.objects if ob.visible_get(view_layer=layer) and ob.select_get(view_layer=layer)]
|
||||
objects = [ob for ob in scene.objects if ob.type in object_filter and ob.visible_get(view_layer=layer) and ob.select_get(view_layer=layer)]
|
||||
else:
|
||||
objects = [ob for ob in scene.objects if ob.visible_get(view_layer=layer)]
|
||||
objects = [ob for ob in scene.objects if ob.type in object_filter and ob.visible_get(view_layer=layer)]
|
||||
|
||||
empty_objects = [ob for ob in objects if ob.type == 'EMPTY']
|
||||
light_objects = [ob for ob in objects if ob.type == 'LIGHT']
|
||||
@ -1567,6 +1726,7 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
if data:
|
||||
matrix = global_matrix @ mtx
|
||||
data.transform(matrix)
|
||||
data.transform(mtx_scale)
|
||||
mesh_objects.append((ob_derived, data, matrix))
|
||||
ma_ls = data.materials
|
||||
ma_ls_len = len(ma_ls)
|
||||
@ -1615,29 +1775,29 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
name_id = {}
|
||||
|
||||
for ob, data, matrix in mesh_objects:
|
||||
translation[ob.name] = ob.location
|
||||
translation[ob.name] = mtx_scale @ ob.location
|
||||
rotation[ob.name] = ob.rotation_euler
|
||||
scale[ob.name] = ob.scale
|
||||
scale[ob.name] = mtx_scale.copy()
|
||||
name_id[ob.name] = len(name_id)
|
||||
object_id[ob.name] = len(object_id)
|
||||
|
||||
for ob in empty_objects:
|
||||
translation[ob.name] = ob.location
|
||||
translation[ob.name] = mtx_scale @ ob.location
|
||||
rotation[ob.name] = ob.rotation_euler
|
||||
scale[ob.name] = ob.scale
|
||||
scale[ob.name] = mtx_scale.copy()
|
||||
name_id[ob.name] = len(name_id)
|
||||
|
||||
for ob in light_objects:
|
||||
translation[ob.name] = ob.location
|
||||
translation[ob.name] = mtx_scale @ ob.location
|
||||
rotation[ob.name] = ob.rotation_euler
|
||||
scale[ob.name] = ob.scale
|
||||
scale[ob.name] = mtx_scale.copy()
|
||||
name_id[ob.name] = len(name_id)
|
||||
object_id[ob.name] = len(object_id)
|
||||
|
||||
for ob in camera_objects:
|
||||
translation[ob.name] = ob.location
|
||||
translation[ob.name] = mtx_scale @ ob.location
|
||||
rotation[ob.name] = ob.rotation_euler
|
||||
scale[ob.name] = ob.scale
|
||||
scale[ob.name] = mtx_scale.copy()
|
||||
name_id[ob.name] = len(name_id)
|
||||
object_id[ob.name] = len(object_id)
|
||||
|
||||
@ -1672,13 +1832,13 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
operator.report({'WARNING'}, "Object %r can't be written into a 3DS file")
|
||||
|
||||
# Export object node
|
||||
if write_keyframe:
|
||||
if use_keyframes:
|
||||
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id))
|
||||
|
||||
i += i
|
||||
|
||||
# Create chunks for all empties - only requires a object node
|
||||
if write_keyframe:
|
||||
if use_keyframes:
|
||||
for ob in empty_objects:
|
||||
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id))
|
||||
|
||||
@ -1687,9 +1847,10 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
object_chunk = _3ds_chunk(OBJECT)
|
||||
obj_light_chunk = _3ds_chunk(OBJECT_LIGHT)
|
||||
color_float_chunk = _3ds_chunk(RGB)
|
||||
light_distance = translation[ob.name]
|
||||
light_energy_factor = _3ds_chunk(LIGHT_MULTIPLIER)
|
||||
object_chunk.add_variable("light", _3ds_string(sane_name(ob.name)))
|
||||
obj_light_chunk.add_variable("location", _3ds_point_3d(ob.location))
|
||||
obj_light_chunk.add_variable("location", _3ds_point_3d(light_distance))
|
||||
color_float_chunk.add_variable("color", _3ds_float_color(ob.data.color))
|
||||
light_energy_factor.add_variable("energy", _3ds_float(ob.data.energy * 0.001))
|
||||
obj_light_chunk.add_subchunk(color_float_chunk)
|
||||
@ -1697,17 +1858,14 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
|
||||
if ob.data.type == 'SPOT':
|
||||
cone_angle = math.degrees(ob.data.spot_size)
|
||||
hotspot = cone_angle - (ob.data.spot_blend * math.floor(cone_angle))
|
||||
hypo = math.copysign(math.sqrt(pow(ob.location.x, 2) + pow(ob.location.y, 2)), ob.location.y)
|
||||
pos_x = -1 * math.copysign(ob.location.x + (ob.location.y * math.tan(ob.rotation_euler.z)), ob.rotation_euler.x)
|
||||
pos_y = -1 * math.copysign(ob.location.y + (ob.location.x * math.tan(math.radians(90) - ob.rotation_euler.z)), ob.rotation_euler.z)
|
||||
pos_z = -1 * math.copysign(hypo * math.tan(math.radians(90) - ob.rotation_euler.x), ob.location.z)
|
||||
hot_spot = cone_angle - (ob.data.spot_blend * math.floor(cone_angle))
|
||||
spot_pos = calc_target(light_distance, rotation[ob.name].x, rotation[ob.name].z)
|
||||
spotlight_chunk = _3ds_chunk(LIGHT_SPOTLIGHT)
|
||||
spot_roll_chunk = _3ds_chunk(LIGHT_SPOT_ROLL)
|
||||
spotlight_chunk.add_variable("target", _3ds_point_3d((pos_x, pos_y, pos_z)))
|
||||
spotlight_chunk.add_variable("hotspot", _3ds_float(round(hotspot, 4)))
|
||||
spotlight_chunk.add_variable("target", _3ds_point_3d(spot_pos))
|
||||
spotlight_chunk.add_variable("hotspot", _3ds_float(round(hot_spot, 4)))
|
||||
spotlight_chunk.add_variable("angle", _3ds_float(round(cone_angle, 4)))
|
||||
spot_roll_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler.y, 6)))
|
||||
spot_roll_chunk.add_variable("roll", _3ds_float(round(rotation[ob.name].y, 6)))
|
||||
spotlight_chunk.add_subchunk(spot_roll_chunk)
|
||||
if ob.data.use_shadow:
|
||||
spot_shadow_flag = _3ds_chunk(LIGHT_SPOT_SHADOWED)
|
||||
@ -1744,7 +1902,7 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
object_info.add_subchunk(object_chunk)
|
||||
|
||||
# Export light and spotlight target node
|
||||
if write_keyframe:
|
||||
if use_keyframes:
|
||||
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id))
|
||||
if ob.data.type == 'SPOT':
|
||||
kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale, name_id))
|
||||
@ -1753,14 +1911,12 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
for ob in camera_objects:
|
||||
object_chunk = _3ds_chunk(OBJECT)
|
||||
camera_chunk = _3ds_chunk(OBJECT_CAMERA)
|
||||
diagonal = math.copysign(math.sqrt(pow(ob.location.x, 2) + pow(ob.location.y, 2)), ob.location.y)
|
||||
focus_x = -1 * math.copysign(ob.location.x + (ob.location.y * math.tan(ob.rotation_euler.z)), ob.rotation_euler.x)
|
||||
focus_y = -1 * math.copysign(ob.location.y + (ob.location.x * math.tan(math.radians(90) - ob.rotation_euler.z)), ob.rotation_euler.z)
|
||||
focus_z = -1 * math.copysign(diagonal * math.tan(math.radians(90) - ob.rotation_euler.x), ob.location.z)
|
||||
camera_distance = translation[ob.name]
|
||||
camera_target = calc_target(camera_distance, rotation[ob.name].x, rotation[ob.name].z)
|
||||
object_chunk.add_variable("camera", _3ds_string(sane_name(ob.name)))
|
||||
camera_chunk.add_variable("location", _3ds_point_3d(ob.location))
|
||||
camera_chunk.add_variable("target", _3ds_point_3d((focus_x, focus_y, focus_z)))
|
||||
camera_chunk.add_variable("roll", _3ds_float(round(ob.rotation_euler.y, 6)))
|
||||
camera_chunk.add_variable("location", _3ds_point_3d(camera_distance))
|
||||
camera_chunk.add_variable("target", _3ds_point_3d(camera_target))
|
||||
camera_chunk.add_variable("roll", _3ds_float(round(rotation[ob.name].y, 6)))
|
||||
camera_chunk.add_variable("lens", _3ds_float(ob.data.lens))
|
||||
object_chunk.add_subchunk(camera_chunk)
|
||||
|
||||
@ -1780,7 +1936,7 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
object_info.add_subchunk(object_chunk)
|
||||
|
||||
# Export camera and target node
|
||||
if write_keyframe:
|
||||
if use_keyframes:
|
||||
kfdata.add_subchunk(make_object_node(ob, translation, rotation, scale, name_id))
|
||||
kfdata.add_subchunk(make_target_node(ob, translation, rotation, scale, name_id))
|
||||
|
||||
@ -1788,11 +1944,10 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
primary.add_subchunk(object_info)
|
||||
|
||||
# Add main keyframe data chunk to primary chunk
|
||||
if write_keyframe:
|
||||
if use_keyframes:
|
||||
primary.add_subchunk(kfdata)
|
||||
|
||||
# At this point, the chunk hierarchy is completely built
|
||||
# Check the size
|
||||
# The chunk hierarchy is completely built, now check the size
|
||||
primary.get_size()
|
||||
|
||||
# Open the file for writing
|
||||
@ -1809,6 +1964,7 @@ def save(operator, context, filepath="", use_selection=False, use_hierarchy=Fals
|
||||
name_mapping.clear()
|
||||
|
||||
# Debugging only: report the exporting time
|
||||
context.window.cursor_set('DEFAULT')
|
||||
print("3ds export time: %.2f" % (time.time() - duration))
|
||||
|
||||
# Debugging only: dump the chunk hierarchy
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@
|
||||
bl_info = {
|
||||
"name": "FBX format",
|
||||
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
|
||||
"version": (5, 5, 0),
|
||||
"version": (5, 5, 1),
|
||||
"blender": (3, 6, 0),
|
||||
"location": "File > Import-Export",
|
||||
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",
|
||||
|
@ -67,6 +67,9 @@ MAT_CONVERT_BONE = Matrix()
|
||||
BLENDER_OTHER_OBJECT_TYPES = {'CURVE', 'SURFACE', 'FONT', 'META'}
|
||||
BLENDER_OBJECT_TYPES_MESHLIKE = {'MESH'} | BLENDER_OTHER_OBJECT_TYPES
|
||||
|
||||
SHAPE_KEY_SLIDER_HARD_MIN = bpy.types.ShapeKey.bl_rna.properties["slider_min"].hard_min
|
||||
SHAPE_KEY_SLIDER_HARD_MAX = bpy.types.ShapeKey.bl_rna.properties["slider_max"].hard_max
|
||||
|
||||
|
||||
# Lamps.
|
||||
FBX_LIGHT_TYPES = {
|
||||
@ -600,6 +603,49 @@ def ensure_object_not_in_edit_mode(context, obj):
|
||||
return True
|
||||
|
||||
|
||||
def expand_shape_key_range(shape_key, value_to_fit):
|
||||
"""Attempt to expand the slider_min/slider_max of a shape key to fit `value_to_fit` within the slider range,
|
||||
expanding slightly beyond `value_to_fit` if possible, so that the new slider_min/slider_max is not the same as
|
||||
`value_to_fit`. Blender has a hard minimum and maximum for slider values, so it may not be possible to fit the value
|
||||
within the slider range.
|
||||
|
||||
If `value_to_fit` is already within the slider range, no changes are made.
|
||||
|
||||
First tries setting slider_min/slider_max to double `value_to_fit`, otherwise, expands the range in the direction of
|
||||
`value_to_fit` by double the distance to `value_to_fit`.
|
||||
|
||||
The new slider_min/slider_max is rounded down/up to the nearest whole number for a more visually pleasing result.
|
||||
|
||||
Returns whether it was possible to expand the slider range to fit `value_to_fit`."""
|
||||
if value_to_fit < (slider_min := shape_key.slider_min):
|
||||
if value_to_fit < 0.0:
|
||||
# For the most common case, set slider_min to double value_to_fit.
|
||||
target_slider_min = value_to_fit * 2.0
|
||||
else:
|
||||
# Doubling value_to_fit would make it larger, so instead decrease slider_min by double the distance between
|
||||
# slider_min and value_to_fit.
|
||||
target_slider_min = slider_min - (slider_min - value_to_fit) * 2.0
|
||||
# Set slider_min to the first whole number less than or equal to target_slider_min.
|
||||
shape_key.slider_min = math.floor(target_slider_min)
|
||||
|
||||
return value_to_fit >= SHAPE_KEY_SLIDER_HARD_MIN
|
||||
elif value_to_fit > (slider_max := shape_key.slider_max):
|
||||
if value_to_fit > 0.0:
|
||||
# For the most common case, set slider_max to double value_to_fit.
|
||||
target_slider_max = value_to_fit * 2.0
|
||||
else:
|
||||
# Doubling value_to_fit would make it smaller, so instead increase slider_max by double the distance between
|
||||
# slider_max and value_to_fit.
|
||||
target_slider_max = slider_max + (value_to_fit - slider_max) * 2.0
|
||||
# Set slider_max to the first whole number greater than or equal to target_slider_max.
|
||||
shape_key.slider_max = math.ceil(target_slider_max)
|
||||
|
||||
return value_to_fit <= SHAPE_KEY_SLIDER_HARD_MAX
|
||||
else:
|
||||
# Value is already within the range.
|
||||
return True
|
||||
|
||||
|
||||
# ##### Attribute utils. #####
|
||||
AttributeDataTypeInfo = namedtuple("AttributeDataTypeInfo", ["dtype", "foreach_attribute", "item_size"])
|
||||
_attribute_data_type_info_lookup = {
|
||||
|
@ -47,6 +47,7 @@ from .fbx_utils import (
|
||||
MESH_ATTRIBUTE_CORNER_VERT,
|
||||
MESH_ATTRIBUTE_SHARP_FACE,
|
||||
MESH_ATTRIBUTE_SHARP_EDGE,
|
||||
expand_shape_key_range,
|
||||
)
|
||||
|
||||
# global singleton, assign on execution
|
||||
@ -563,7 +564,7 @@ def blen_read_animations_curves_iter(fbx_curves, blen_start_offset, fbx_start_of
|
||||
yield (curr_blenkframe, curr_values)
|
||||
|
||||
|
||||
def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, global_scale):
|
||||
def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, global_scale, shape_key_deforms):
|
||||
"""
|
||||
'Bake' loc/rot/scale into the action,
|
||||
taking any pre_ and post_ matrix into account to transform from fbx into blender space.
|
||||
@ -635,12 +636,14 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
store_keyframe(fc, frame, v)
|
||||
|
||||
elif isinstance(item, ShapeKey):
|
||||
deform_values = shape_key_deforms.setdefault(item, [])
|
||||
for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps):
|
||||
value = 0.0
|
||||
for v, (fbxprop, channel, _fbx_acdata) in values:
|
||||
assert(fbxprop == b'DeformPercent')
|
||||
assert(channel == 0)
|
||||
value = v / 100.0
|
||||
deform_values.append(value)
|
||||
|
||||
for fc, v in zip(blen_curves, (value,)):
|
||||
store_keyframe(fc, frame, v)
|
||||
@ -741,6 +744,7 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o
|
||||
"""
|
||||
from bpy.types import ShapeKey, Material, Camera
|
||||
|
||||
shape_key_values = {}
|
||||
actions = {}
|
||||
for as_uuid, ((fbx_asdata, _blen_data), alayers) in stacks.items():
|
||||
stack_name = elem_name_ensure_class(fbx_asdata, b'AnimStack')
|
||||
@ -778,7 +782,22 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o
|
||||
if not id_data.animation_data.action:
|
||||
id_data.animation_data.action = action
|
||||
# And actually populate the action!
|
||||
blen_read_animations_action_item(action, item, cnodes, scene.render.fps, anim_offset, global_scale)
|
||||
blen_read_animations_action_item(action, item, cnodes, scene.render.fps, anim_offset, global_scale,
|
||||
shape_key_values)
|
||||
|
||||
# If the minimum/maximum animated value is outside the slider range of the shape key, attempt to expand the slider
|
||||
# range until the animated range fits and has extra room to be decreased or increased further.
|
||||
# Shape key slider_min and slider_max have hard min/max values, if an imported animation uses a value outside that
|
||||
# range, a warning message will be printed to the console and the slider_min/slider_max values will end up clamped.
|
||||
shape_key_values_in_range = True
|
||||
for shape_key, deform_values in shape_key_values.items():
|
||||
min_animated_deform = min(deform_values)
|
||||
max_animated_deform = max(deform_values)
|
||||
shape_key_values_in_range &= expand_shape_key_range(shape_key, min_animated_deform)
|
||||
shape_key_values_in_range &= expand_shape_key_range(shape_key, max_animated_deform)
|
||||
if not shape_key_values_in_range:
|
||||
print("WARNING: The imported animated Value of a Shape Key is beyond the minimum/maximum allowed and will be"
|
||||
" clamped during playback.")
|
||||
|
||||
|
||||
# ----
|
||||
@ -1599,6 +1618,9 @@ def blen_read_shapes(fbx_tmpl, fbx_data, objects, me, scene):
|
||||
objects = list({node.bl_obj for node in objects})
|
||||
assert(objects)
|
||||
|
||||
# Blender has a hard minimum and maximum shape key Value. If an imported shape key has a value outside this range it
|
||||
# will be clamped, and we'll print a warning message to the console.
|
||||
shape_key_values_in_range = True
|
||||
bc_uuid_to_keyblocks = {}
|
||||
for bc_uuid, fbx_sdata, fbx_bcdata in fbx_data:
|
||||
elem_name_utf8 = elem_name_ensure_class(fbx_sdata, b'Geometry')
|
||||
@ -1644,6 +1666,8 @@ def blen_read_shapes(fbx_tmpl, fbx_data, objects, me, scene):
|
||||
shape_cos[indices] += dvcos
|
||||
kb.data.foreach_set("co", shape_cos.ravel())
|
||||
|
||||
shape_key_values_in_range &= expand_shape_key_range(kb, weight)
|
||||
|
||||
kb.value = weight
|
||||
|
||||
# Add vgroup if necessary.
|
||||
@ -1657,6 +1681,11 @@ def blen_read_shapes(fbx_tmpl, fbx_data, objects, me, scene):
|
||||
kb.vertex_group = kb.name
|
||||
|
||||
bc_uuid_to_keyblocks.setdefault(bc_uuid, []).append(kb)
|
||||
|
||||
if not shape_key_values_in_range:
|
||||
print("WARNING: The imported Value of a Shape Key on the Mesh '%s' is beyond the minimum/maximum allowed and"
|
||||
" has been clamped." % me.name)
|
||||
|
||||
return bc_uuid_to_keyblocks
|
||||
|
||||
|
||||
|
@ -466,7 +466,7 @@ class ExportGLTF2_Base(ConvertGLTF2_Base):
|
||||
export_optimize_animation_keep_anim_armature: BoolProperty(
|
||||
name='Force keeping channels for bones',
|
||||
description=(
|
||||
"if all keyframes are identical in a rig, "
|
||||
"If all keyframes are identical in a rig, "
|
||||
"force keeping the minimal animation. "
|
||||
"When off, all possible channels for "
|
||||
"the bones will be exported, even if empty "
|
||||
|
@ -115,12 +115,11 @@ def get_bmesh_loosevert_array(bm):
|
||||
|
||||
|
||||
class _Mesh_Arrays:
|
||||
def __init__(self, obj, create_tris, create_edges, create_looseverts):
|
||||
def __init__(self, depsgraph, obj, create_tris, create_edges, create_looseverts):
|
||||
self.tri_verts = self.edge_verts = self.looseverts = ()
|
||||
if obj.type == "MESH":
|
||||
me = obj.data
|
||||
if me.is_editmode:
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
if obj.data.is_editmode:
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
self.verts_co = get_bmesh_vert_co_array(bm)
|
||||
@ -131,11 +130,9 @@ class _Mesh_Arrays:
|
||||
self.edge_verts = get_bmesh_edge_verts_array(bm)
|
||||
if create_looseverts:
|
||||
self.looseverts = get_bmesh_loosevert_array(bm)
|
||||
|
||||
del bm
|
||||
else:
|
||||
import bpy
|
||||
|
||||
ob_eval = obj.evaluated_get(depsgraph)
|
||||
me = ob_eval.data
|
||||
self.verts_co = get_mesh_vert_co_array(me)
|
||||
|
||||
if create_tris:
|
||||
@ -177,21 +174,20 @@ class GPU_Indices_Mesh:
|
||||
"users",
|
||||
)
|
||||
|
||||
_Hash = {}
|
||||
_cache = {}
|
||||
shader = None
|
||||
UBO_data = None
|
||||
UBO = None
|
||||
|
||||
@classmethod
|
||||
def end_opengl(cls):
|
||||
def gpu_data_free(cls):
|
||||
del cls.shader
|
||||
del cls.UBO
|
||||
del cls.UBO_data
|
||||
|
||||
del cls
|
||||
cls.shader = cls.UBO = cls.UBO_data = None
|
||||
|
||||
@staticmethod
|
||||
def init_opengl():
|
||||
def gpu_data_ensure():
|
||||
cls = GPU_Indices_Mesh
|
||||
# OpenGL was already initialized, nothing to do here.
|
||||
if cls.shader is not None:
|
||||
@ -200,8 +196,8 @@ class GPU_Indices_Mesh:
|
||||
import atexit
|
||||
|
||||
# Make sure we only registered the callback once.
|
||||
atexit.unregister(cls.end_opengl)
|
||||
atexit.register(cls.end_opengl)
|
||||
atexit.unregister(cls.gpu_data_free)
|
||||
atexit.register(cls.gpu_data_free)
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
|
||||
@ -280,8 +276,8 @@ class GPU_Indices_Mesh:
|
||||
def __init__(self, depsgraph, obj, draw_tris, draw_edges, draw_verts):
|
||||
self.ob_data = obj.original.data
|
||||
|
||||
if self.ob_data in GPU_Indices_Mesh._Hash:
|
||||
src = GPU_Indices_Mesh._Hash[self.ob_data]
|
||||
if self.ob_data in GPU_Indices_Mesh._cache:
|
||||
src = GPU_Indices_Mesh._cache[self.ob_data]
|
||||
dst = self
|
||||
|
||||
dst.draw_tris = src.draw_tris
|
||||
@ -298,10 +294,10 @@ class GPU_Indices_Mesh:
|
||||
dst.users.append(self)
|
||||
|
||||
update = False
|
||||
|
||||
else:
|
||||
GPU_Indices_Mesh._Hash[self.ob_data] = self
|
||||
GPU_Indices_Mesh._cache[self.ob_data] = self
|
||||
self.users = [self]
|
||||
|
||||
update = True
|
||||
|
||||
if update:
|
||||
@ -309,12 +305,10 @@ class GPU_Indices_Mesh:
|
||||
self.draw_edges = draw_edges
|
||||
self.draw_verts = draw_verts
|
||||
|
||||
GPU_Indices_Mesh.init_opengl()
|
||||
GPU_Indices_Mesh.gpu_data_ensure()
|
||||
|
||||
## Init Array ##
|
||||
mesh_arrays = _Mesh_Arrays(
|
||||
depsgraph.id_eval_get(obj), draw_tris, draw_edges, draw_verts
|
||||
)
|
||||
mesh_arrays = _Mesh_Arrays(depsgraph, obj, draw_tris, draw_edges, draw_verts)
|
||||
|
||||
if mesh_arrays.verts_co is None:
|
||||
self.draw_tris = False
|
||||
@ -479,13 +473,13 @@ class GPU_Indices_Mesh:
|
||||
del self.tri_verts
|
||||
del self.edge_verts
|
||||
del self.looseverts
|
||||
GPU_Indices_Mesh._Hash.pop(self.ob_data)
|
||||
GPU_Indices_Mesh._cache.pop(self.ob_data)
|
||||
|
||||
# print('mesh_del', self.obj.name)
|
||||
|
||||
|
||||
def gpu_Indices_enable_state(winmat, viewmat):
|
||||
GPU_Indices_Mesh.init_opengl()
|
||||
GPU_Indices_Mesh.gpu_data_ensure()
|
||||
gpu.matrix.push()
|
||||
gpu.matrix.push_projection()
|
||||
gpu.matrix.load_projection_matrix(winmat)
|
||||
@ -499,7 +493,7 @@ def gpu_Indices_restore_state():
|
||||
|
||||
|
||||
def gpu_Indices_use_clip_planes(rv3d, value):
|
||||
GPU_Indices_Mesh.init_opengl()
|
||||
GPU_Indices_Mesh.gpu_data_ensure()
|
||||
if value and rv3d.use_clip_planes:
|
||||
GPU_Indices_Mesh.UBO_data.use_clip_planes = True
|
||||
GPU_Indices_Mesh.UBO_data.WorldClipPlanes[0] = rv3d.clip_planes[0][:]
|
||||
@ -513,4 +507,4 @@ def gpu_Indices_use_clip_planes(rv3d, value):
|
||||
|
||||
|
||||
def gpu_Indices_mesh_cache_clear():
|
||||
GPU_Indices_Mesh._Hash.clear()
|
||||
GPU_Indices_Mesh._cache.clear()
|
||||
|
@ -76,11 +76,9 @@ def main_object(context, collection, obj, level, **kw):
|
||||
|
||||
# must apply after boolean.
|
||||
if use_recenter:
|
||||
bpy.ops.object.origin_set(
|
||||
{"selected_editable_objects": objects},
|
||||
type='ORIGIN_GEOMETRY',
|
||||
center='MEDIAN',
|
||||
)
|
||||
context_override = {"selected_editable_objects": objects}
|
||||
with bpy.context.temp_override(**context_override):
|
||||
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
|
||||
|
||||
# ----------
|
||||
# Recursion
|
||||
|
@ -98,7 +98,8 @@ else:
|
||||
addon_prefs = context.preferences.addons[__package__].preferences
|
||||
|
||||
if addon_prefs.show_overlays and sun_props.show_north:
|
||||
_north_handle = bpy.types.SpaceView3D.draw_handler_add(north_draw, (), 'WINDOW', 'POST_VIEW')
|
||||
if _north_handle is None:
|
||||
_north_handle = bpy.types.SpaceView3D.draw_handler_add(north_draw, (), 'WINDOW', 'POST_VIEW')
|
||||
elif _north_handle is not None:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(_north_handle, 'WINDOW')
|
||||
_north_handle = None
|
||||
|
@ -5,8 +5,8 @@
|
||||
bl_info = {
|
||||
"name": "Manage UI translations",
|
||||
"author": "Bastien Montagne",
|
||||
"version": (1, 3, 3),
|
||||
"blender": (3, 6, 0),
|
||||
"version": (1, 3, 4),
|
||||
"blender": (4, 0, 0),
|
||||
"location": "Main \"File\" menu, text editor, any UI control",
|
||||
"description": "Allows managing UI translations directly from Blender "
|
||||
"(update main .po files, update scripts' translations, etc.)",
|
||||
|
@ -210,7 +210,11 @@ class UI_OT_i18n_updatetranslation_svn_trunk(Operator):
|
||||
if uid and uid not in stats:
|
||||
po = utils_i18n.I18nMessages(uid=uid, kind='PO', src=po_path, settings=self.settings)
|
||||
stats[uid] = po.nbr_trans_msgs / po.nbr_msgs if po.nbr_msgs > 0 else 0
|
||||
utils_languages_menu.gen_menu_file(stats, self.settings)
|
||||
languages_menu_lines = utils_languages_menu.gen_menu_file(stats, self.settings)
|
||||
with open(os.path.join(self.settings.TRUNK_MO_DIR, self.settings.LANGUAGES_FILE), 'w', encoding="utf8") as f:
|
||||
f.write("\n".join(languages_menu_lines))
|
||||
with open(os.path.join(self.settings.GIT_I18N_ROOT, self.settings.LANGUAGES_FILE), 'w', encoding="utf8") as f:
|
||||
f.write("\n".join(languages_menu_lines))
|
||||
context.window_manager.progress_end()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
Loading…
Reference in New Issue
Block a user