Repe [T3DU] Update - 31f26d171bba0355ce2a77031e3aad4c64dbc7e9

This commit is contained in:
2026-09-04 10:46:13 -07:00
parent c915312901
commit 0460b9d587
482 changed files with 27797 additions and 3226 deletions

View File

@ -64,13 +64,14 @@ class NodeType(Enum):
SPEAKER = 5
DECAL = 6
PROBE = 7
CURVE = 8
@classmethod
def get_bobject_type(cls, bobject: bpy.types.Object) -> "NodeType":
"""Returns the NodeType enum member belonging to the type of
the given blender object."""
if bobject.type == "MESH":
if bobject.data.polygons or bobject.data.edges or bobject.data.vertices:
if bobject.data.polygons: #or bobject.data.edges or bobject.data.vertices:
return cls.MESH
elif bobject.type in ('FONT', 'META', 'CURVE'): # FIXME: curves with meshes shouldn't be used in modifiers for now.
if bobject.type == 'CURVE':
@ -89,12 +90,14 @@ class NodeType(Enum):
return cls.SPEAKER
elif bobject.type == "LIGHT_PROBE":
return cls.PROBE
elif bobject.type == "CURVE":
return cls.CURVE
return cls.EMPTY
STRUCT_IDENTIFIER = ("object", "bone_object", "mesh_object",
"light_object", "camera_object", "speaker_object",
"decal_object", "probe_object")
"decal_object", "probe_object", "curve_object")
# Internal target names for single FCurve data paths
FCURVE_TARGET_NAMES = {
@ -161,6 +164,7 @@ class LeenkxExporter:
self.bobject_array: Dict[bpy.types.Object, Dict[str, Union[NodeType, str]]] = {}
self.bobject_bone_array = {}
self.mesh_array = {}
self.curve_array = {}
self.light_array = {}
self.probe_array = {}
self.camera_array = {}
@ -405,7 +409,7 @@ class LeenkxExporter:
o['transform'] = {'values': LeenkxExporter.write_matrix(matrix_local)}
# Animated transform
if bobject.animation_data is not None and bobject.type != "ARMATURE":
if bobject.animation_data is not None and bobject.type != "ARMATURE" and bobject.lnx_animation_enabled:
action = bobject.animation_data.action
if action is not None:
@ -987,7 +991,7 @@ class LeenkxExporter:
out_object = self.object_to_lnx_object_dict[bobject]
out_object['type'] = STRUCT_IDENTIFIER[object_type.value]
out_object['name'] = bobject_ref["structName"]
out_object['name'] = lnx.utils.safestr(bobject_ref["structName"])
if bobject.parent_type == "BONE":
out_object['parent_bone'] = bobject.parent_bone
@ -1064,7 +1068,7 @@ class LeenkxExporter:
_num_verts = len(_verts)
_all_co = np.empty(_num_verts * 3, dtype='<f8')
_verts.foreach_get('co', _all_co)
_all_str = _all_co.astype('U32').tolist()
_all_f = _all_co.tolist()
_num_groups = len(bobject.vertex_groups)
_vg_idx = [[] for _ in range(_num_groups)]
for v in _verts:
@ -1079,9 +1083,9 @@ class LeenkxExporter:
_values = []
for vi in indices:
base = vi * 3
_values.append(_all_str[base])
_values.append(_all_str[base + 1])
_values.append(_all_str[base + 2])
_values.append(_all_f[base])
_values.append(_all_f[base + 1])
_values.append(_all_f[base + 2])
else:
_values = []
out_object['vertex_groups'].append({
@ -1100,16 +1104,24 @@ class LeenkxExporter:
if len(bobject.lnx_propertylist) > 0:
out_object['properties'] = []
for proplist_item in bobject.lnx_propertylist:
# Check if the property is a collection (array type).
if proplist_item.type_prop == 'array':
# Convert the collection to a list.
array_type = proplist_item.array_item_type
collection_value = getattr(proplist_item, 'array_prop')
property_name = array_type + '_prop'
value = [str(getattr(item, property_name)) for item in collection_value]
if array_type == 'vector':
value = []
for item in collection_value:
v = getattr(item, property_name)
value.append([float(v[0]), float(v[1]), float(v[2])])
else:
value = [getattr(item, property_name) for item in collection_value]
elif proplist_item.type_prop == 'vector':
v = getattr(proplist_item, 'vector_prop')
value = [float(v[0]), float(v[1]), float(v[2])]
else:
# Handle other types of properties.
value = getattr(proplist_item, proplist_item.type_prop + '_prop')
out_property = {
@ -1163,6 +1175,9 @@ class LeenkxExporter:
# Decal flag
if mat is not None and mat.lnx_decal:
out_object['type'] = 'decal_object'
if mat is not None:
if 'Rot' in bobject.lnx_instanced:
mat.lnx_instanced_rot_type = bobject.lnx_instanced_rot_type
# No material, mimic cycles and assign default
if len(out_object['material_refs']) == 0:
self.use_default_material(bobject, out_object)
@ -1228,15 +1243,32 @@ class LeenkxExporter:
self.speaker_array[objref]["objectTable"].append(bobject)
out_object['data_ref'] = self.speaker_array[objref]["structName"]
elif object_type is NodeType.CURVE:
if objref not in self.curve_array:
self.curve_array[objref] = {"structName" : objname, "objectTable" : [bobject]}
else:
self.curve_array[objref]["objectTable"].append(bobject)
out_object['data_ref'] = lnx.utils.safestr(self.curve_array[objref]["structName"])
out_object['material_refs'] = []
for i in range(len(bobject.material_slots)):
mat = self.slot_to_material(bobject, bobject.material_slots[i])
# Export ref
self.export_material_ref(bobject, mat, i, out_object)
# Decal flag
if mat is not None and mat.lnx_decal:
out_object['type'] = 'decal_object'
# Export the transform. If object is animated, then animation tracks are exported here
if bobject.type != 'ARMATURE' and bobject.animation_data is not None:
action = bobject.animation_data.action
export_actions = [action]
for track in bobject.animation_data.nla_tracks:
if track.strips is None:
if track.strips is None or track.mute:
continue
for strip in track.strips:
if strip.action is None or strip.action in export_actions:
if strip.action is None or strip.action in export_actions or strip.mute:
continue
export_actions.append(strip.action)
orig_action = action
@ -1265,7 +1297,7 @@ class LeenkxExporter:
bone_translation_pose = pose_bone.tail - pose_bone.head
out_object['parent_bone_tail_pose'] = [bone_translation_pose[0], bone_translation_pose[1], bone_translation_pose[2]]
if bobject.type == 'ARMATURE' and bobject.data is not None:
if lnx.utils.get_rp().lnx_skin != 'Off' and bobject.type == 'ARMATURE' and bobject.data is not None:
# Armature data
bdata = bobject.data
# Reference start action
@ -1290,10 +1322,10 @@ class LeenkxExporter:
# armature object to deform with
if hasattr(adata, 'nla_tracks') and adata.nla_tracks is not None:
for track in adata.nla_tracks:
if track.strips is None:
if track.strips is None or track.mute:
continue
for strip in track.strips:
if strip.action is None:
if strip.action is None or strip.mute:
continue
if strip.action.name == action.name:
continue
@ -2196,6 +2228,86 @@ class LeenkxExporter:
except ReferenceError:
pass
def export_curve(self, object_ref):
"""Exports a single curve object."""
table = object_ref[1]["objectTable"]
bobject = table[0]
curve_id = lnx.utils.safestr(object_ref[1]["structName"])
world = bpy.data.worlds['Lnx']
if world.lnx_verbose_output:
print('Exporting curve ' + lnx.utils.asset_name(bobject.data))
curve_data = bobject.data
out_curve = {
'name': curve_id,
'object': bobject.name,
'splines': [],
'strength': curve_data.lnx_draw_strength,
'color': [curve_data.lnx_draw_color[0], curve_data.lnx_draw_color[1], curve_data.lnx_draw_color[2], curve_data.lnx_draw_color[3]]
}
for spline in curve_data.splines:
if spline.type == 'BEZIER':
current_spline = {
'closed': spline.use_cyclic_u,
'resolution': spline.resolution_u,
'points': [],
'material_index': spline.material_index,
}
for bezier_point in spline.bezier_points:
point_data = {
'co': [bezier_point.co.x, bezier_point.co.y, bezier_point.co.z],
'handle_left': [bezier_point.handle_left.x, bezier_point.handle_left.y, bezier_point.handle_left.z],
'handle_right': [bezier_point.handle_right.x, bezier_point.handle_right.y, bezier_point.handle_right.z]
}
current_spline['points'].append(point_data)
out_curve['splines'].append(current_spline)
active_keys = [kb for kb in bobject.data.shape_keys.key_blocks if not kb.mute] if bobject.data.shape_keys else []
if len(active_keys) == 0:
temp_obj = bobject.to_mesh()
if len(temp_obj.polygons) > 0:
temp_ref = [object_ref[0], {
"objectTable": table,
"structName": lnx.utils.safestr(curve_id)
}]
self.export_mesh(temp_ref)
bobject.to_mesh_clear()
else:
out_curve['shape_keys'] = []
for kb in bobject.data.shape_keys.key_blocks:
key_data = {
'name': kb.name,
'value': kb.value,
'points': []
}
for p in kb.data:
key_data['points'].append({
'co': p.co[:],
'handle_left': p.handle_left[:],
'handle_right': p.handle_right[:]
})
out_curve['shape_keys'].append(key_data)
mat_count = len(bobject.material_slots)
if mat_count > 0:
out_curve['material_refs'] = []
for i in range(mat_count):
mat = self.slot_to_material(bobject, bobject.material_slots[i])
self.export_material_ref(bobject, mat, i, out_curve)
self.output['curve_datas'].append(out_curve)
def export_light(self, object_ref):
"""Exports a single light object."""
rpdat = lnx.utils.get_rp()
@ -2288,11 +2400,11 @@ class LeenkxExporter:
o = {'name': objectRef[1]["structName"]}
bo = objectRef[0]
if bo.type == 'GRID':
if bo.type in ('GRID', 'VOLUME'):
o['type'] = 'grid'
elif bo.type == 'PLANAR':
elif bo.type in ('PLANAR', 'PLANE'):
o['type'] = 'planar'
else:
else: # (CUBEMAP, SPHERE)
o['type'] = 'cubemap'
self.output['probe_datas'].append(o)
@ -2348,6 +2460,7 @@ class LeenkxExporter:
if bobject.type == 'CAMERA':
self.output['camera_ref'] = asset_name
self.has_spawning_camera = True
asset_name = bobject.name
out_collection['object_refs'].append(asset_name)
@ -2731,7 +2844,7 @@ class LeenkxExporter:
'lifetime_random': psettings.lifetime_random,
'emit_from': emit_from,
# Velocity
# 'normal_factor': psettings.normal_factor,
'normal_factor': psettings.normal_factor,
# 'tangent_factor': psettings.tangent_factor,
# 'tangent_phase': psettings.tangent_phase,
'object_align_factor': (
@ -2815,20 +2928,19 @@ class LeenkxExporter:
def export_world(self):
"""Exports the world of the current scene."""
world = self.scene.world
for world in bpy.data.worlds:
if world is not None:
world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name)
if world is not None:
world_name = lnx.utils.safestr(lnx.utils.asset_name(world) if world.library else world.name)
if world_name not in self.world_array:
self.world_array.append(world_name)
out_world = {'name': world_name}
if world_name not in self.world_array:
self.world_array.append(world_name)
out_world = {'name': world_name}
self.post_export_world(world, out_world)
self.output['world_datas'].append(out_world)
self.post_export_world(world, out_world)
self.output['world_datas'].append(out_world)
elif lnx.utils.get_rp().rp_background == 'World':
log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared')
elif lnx.utils.get_rp().rp_background == 'World':
log.warn(f'Scene "{self.scene.name}" is missing a world, some render targets will not be cleared')
def export_objects(self, scene):
"""Exports all supported blender objects.
@ -2847,6 +2959,7 @@ class LeenkxExporter:
self.output['light_datas'] = []
self.output['camera_datas'] = []
self.output['speaker_datas'] = []
self.output['curve_datas'] = []
for light_ref in self.light_array.items():
self.export_light(light_ref)
@ -2867,6 +2980,9 @@ class LeenkxExporter:
for lightprobe_object in self.probe_array.items():
self.export_probe(lightprobe_object)
for curve_ref in self.curve_array.items():
self.export_curve(curve_ref)
self.output['mesh_datas'] = []
for mesh_ref in self.mesh_array.items():
self.export_mesh(mesh_ref)
@ -2896,9 +3012,9 @@ class LeenkxExporter:
self.process_bobject(bobject)
# Softbody needs connected triangles, use optimized
# geometry export
for mod in bobject.modifiers:
"""for mod in bobject.modifiers:
if mod.type in ('CLOTH', 'SOFT_BODY'):
LeenkxExporter.optimize_enabled = True
LeenkxExporter.optimize_enabled = True"""
self.process_skinned_meshes()
@ -3029,6 +3145,8 @@ class LeenkxExporter:
self.export_scene_traits()
self.export_scene_properties()
self.export_canvas_themes()
# Write embedded data references
@ -3145,6 +3263,14 @@ class LeenkxExporter:
instanced_type = 4
instanced_data = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]
attr_data_block = []
for attr in bobject.lnx_instanced_attrs:
attr_data_block.extend([0.0] * attr.size)
instanced_data.extend(attr_data_block)
if lnx.utils.export_morph_targets(bobject):
instanced_data.extend([0.0, 0.0, 0.0, 0.0])
for child in bobject.children:
if not child.lnx_export or child.hide_render:
continue
@ -3163,6 +3289,9 @@ class LeenkxExporter:
instanced_data.append(scale.x)
instanced_data.append(scale.y)
instanced_data.append(scale.z)
instanced_data.extend(attr_data_block)
if lnx.utils.export_morph_targets(child):
instanced_data.extend([0.0, 0.0, 0.0, 0.0])
break
# Instance render collections with same children?
@ -3338,9 +3467,9 @@ class LeenkxExporter:
# Phys traits
if phys_enabled:
for modifier in bobject.modifiers:
if modifier.type in ('CLOTH', 'SOFT_BODY'):
if modifier.type in ('CLOTH', 'SOFT_BODY') and modifier.show_render:
self.add_softbody_mod(o, bobject, modifier)
elif modifier.type == 'HOOK':
elif modifier.type == 'HOOK' and modifier.show_render:
self.add_hook_mod(o, bobject, modifier.object.name, modifier.vertex_group)
# Rigid body constraint
@ -3392,7 +3521,7 @@ class LeenkxExporter:
if bone:
out_constraint['bone'] = bobject.name
if hasattr(constraint, 'target') and constraint.target is not None:
if constraint.type == 'COPY_LOCATION':
if constraint.type in ('COPY_LOCATION', 'COPY_ROTATION'):
out_constraint['target'] = constraint.target.name
out_constraint['use_x'] = constraint.use_x
out_constraint['use_y'] = constraint.use_y
@ -3402,9 +3531,38 @@ class LeenkxExporter:
out_constraint['invert_z'] = constraint.invert_z
out_constraint['use_offset'] = constraint.use_offset
out_constraint['influence'] = constraint.influence
elif constraint.type in ('COPY_SCALE'):
out_constraint['target'] = constraint.target.name
out_constraint['use_x'] = constraint.use_x
out_constraint['use_y'] = constraint.use_y
out_constraint['use_z'] = constraint.use_z
out_constraint['use_offset'] = constraint.use_offset
out_constraint['influence'] = constraint.influence
elif constraint.type == 'COPY_TRANSFORMS':
out_constraint['target'] = constraint.target.name
out_constraint['influence'] = constraint.influence
elif constraint.type == 'CHILD_OF':
out_constraint['target'] = constraint.target.name
out_constraint['influence'] = constraint.influence
elif constraint.type in ('LIMIT_LOCATION', 'LIMIT_ROTATION', 'LIMIT_SCALE'):
if constraint.type == 'LIMIT_ROTATION':
out_constraint['use_limit_x'] = constraint.use_limit_x
out_constraint['use_limit_y'] = constraint.use_limit_y
out_constraint['use_limit_z'] = constraint.use_limit_z
else:
out_constraint['use_min_x'] = constraint.use_min_x
out_constraint['use_min_y'] = constraint.use_min_y
out_constraint['use_min_z'] = constraint.use_min_z
out_constraint['use_max_x'] = constraint.use_max_x
out_constraint['use_max_y'] = constraint.use_max_y
out_constraint['use_max_z'] = constraint.use_max_z
out_constraint['min_x'] = constraint.min_x
out_constraint['min_y'] = constraint.min_y
out_constraint['min_z'] = constraint.min_z
out_constraint['max_x'] = constraint.max_x
out_constraint['max_y'] = constraint.max_y
out_constraint['max_z'] = constraint.max_z
out_constraint['influence'] = constraint.influence
o['constraints'].append(out_constraint)
@ -3632,6 +3790,35 @@ class LeenkxExporter:
for out_trait in self.output['traits']:
LeenkxExporter.import_traits.append(out_trait['class_name'])
def export_scene_properties(self) -> None:
bscene = self.scene
if len(bscene.lnx_propertylist) > 0:
if 'properties' not in self.output:
self.output['properties'] = []
for proplist_item in bscene.lnx_propertylist:
if proplist_item.type_prop == 'array':
array_type = proplist_item.array_item_type
collection_value = getattr(proplist_item, 'array_prop')
property_name = array_type + '_prop'
if array_type == 'vector':
value = [[v for v in getattr(item, property_name)] for item in collection_value]
else:
value = [getattr(item, property_name) for item in collection_value]
elif proplist_item.type_prop == 'vector':
value = [v for v in getattr(proplist_item, 'vector_prop')]
else:
value = getattr(proplist_item, proplist_item.type_prop + '_prop')
out_property = {
'name': proplist_item.name_prop,
'value': value
}
self.output['properties'].append(out_property)
@staticmethod
def export_canvas_themes():
path_themes = os.path.join(lnx.utils.get_fp(), 'Bundled', 'canvas')
@ -3657,16 +3844,21 @@ class LeenkxExporter:
# ClothModifier
if modifier.type == 'CLOTH':
bend = modifier.settings.bending_stiffness
friction = bobject.lnx_soft_body_friction
damping = bobject.lnx_soft_body_damping
soft_type = 0
# SoftBodyModifier
elif modifier.type == 'SOFT_BODY':
bend = (modifier.settings.bend + 1.0) * 10
bend = modifier.settings.bend
friction = modifier.settings.friction
damping = modifier.settings.goal_friction
soft_type = 1
else:
# Wrong modifier type
return
out_trait['parameters'] = [str(soft_type), str(bend), str(modifier.settings.mass), str(bobject.lnx_soft_body_margin)]
out_trait['parameters'] = [str(soft_type), str(bend), str(modifier.settings.mass), str(bobject.lnx_soft_body_margin), str(friction), str(damping),
str(bobject.lnx_soft_body_linear_stiffness), str(bobject.lnx_soft_body_angular_stiffness), str(bobject.lnx_soft_body_pressure)]
o['traits'].append(out_trait)
if soft_type == 0: