diff --git a/leenkx/Sources/iron2/tools/io_export_lnx.py b/leenkx/Sources/iron2/tools/io_export_lnx.py deleted file mode 100644 index 5df9c8a..0000000 --- a/leenkx/Sources/iron2/tools/io_export_lnx.py +++ /dev/null @@ -1,861 +0,0 @@ -"""Leenkx Mesh Exporter""" -# -# https://leenkx.com/ -# -# Based on Open Game Engine Exchange -# https://opengex.org/ -# Export plugin for Blender by Eric Lengyel -# Copyright 2015, Terathon Software LLC -# -# This software is licensed under the Creative Commons -# Attribution-ShareAlike 3.0 Unported License: -# http://creativecommons.org/licenses/by-sa/3.0/deed.en_US - -import io -import os -import struct -import time -import bpy -from bpy_extras.io_utils import ExportHelper -from mathutils import Vector -import numpy as np - -bl_info = { - "name": "Leenkx Mesh Exporter", - "category": "Import-Export", - "location": "File -> Export", - "description": "Leenkx mesh data", - "author": "Leenkx3D.org", - "version": (2022, 12, 0), - "blender": (3, 3, 0), - "doc_url": "https://github.com/leenkx3d/iron/wiki", - "tracker_url": "https://github.com/leenkx3d/iron/issues", -} - -NodeTypeBone = 1 -NodeTypeMesh = 2 -structIdentifier = ["object", "bone_object", "mesh_object"] - - -class LeenkxExporter(bpy.types.Operator, ExportHelper): - """Export to Leenkx format""" - - bl_idname = "export_scene.lnx" - bl_label = "Export Leenkx" - filename_ext = ".lnx" - - def execute(self, context): - profile_time = time.time() - current_frame = context.scene.frame_current - current_subframe = context.scene.frame_subframe - self.scene = context.scene - self.output = {} - self.bobjectArray = {} - self.bobjectBoneArray = {} - self.meshArray = {} - self.boneParentArray = {} - self.bone_tracks = [] - self.depsgraph = context.evaluated_depsgraph_get() - scene_objects = self.scene.collection.all_objects - - for bobject in scene_objects: - if not bobject.parent: - self.process_bobject(bobject) - - self.process_skinned_meshes() - - self.output["name"] = self.scene.name - self.output["objects"] = [] - for bo in scene_objects: - if not bo.parent: - self.export_object(bo, self.scene) - - self.output["mesh_datas"] = [] - for o in self.meshArray.items(): - self.export_mesh(o) - - self.write_lnx(self.filepath, self.output) - self.scene.frame_set(current_frame, subframe=current_subframe) - - print(f"Scene exported in {str(time.time() - profile_time)}") - - return {"FINISHED"} - - def write_lnx(self, filepath, output): - with open(filepath, "wb") as f: - f.write(packb(output)) - - def write_matrix(self, matrix): - return [ - matrix[0][0], - matrix[0][1], - matrix[0][2], - matrix[0][3], - matrix[1][0], - matrix[1][1], - matrix[1][2], - matrix[1][3], - matrix[2][0], - matrix[2][1], - matrix[2][2], - matrix[2][3], - matrix[3][0], - matrix[3][1], - matrix[3][2], - matrix[3][3], - ] - - def find_bone(self, name): - return next( - ( - bobject_ref - for bobject_ref in self.bobjectBoneArray.items() - if bobject_ref[0].name == name - ), - None, - ) - - def collect_bone_animation(self, armature, name): - path = 'pose.bones["' + name + '"].' - curve_array = [] - if armature.animation_data: - if action := armature.animation_data.action: - curve_array.extend( - fcurve - for fcurve in action.fcurves - if fcurve.data_path.startswith(path) - ) - return curve_array - - def export_bone(self, armature, bone, scene, o, action): - if bobjectRef := self.bobjectBoneArray.get(bone): - o["type"] = structIdentifier[bobjectRef["objectType"]] - o["name"] = bobjectRef["structName"] - self.export_bone_transform(armature, bone, o, action) - o["children"] = [] - for subbobject in bone.children: - so = {} - self.export_bone(armature, subbobject, scene, so, action) - o["children"].append(so) - - def export_pose_markers(self, oanim, action): - if action.pose_markers is None or len(action.pose_markers) == 0: - return - oanim["marker_frames"] = [] - oanim["marker_names"] = [] - for m in action.pose_markers: - oanim["marker_frames"].append(int(m.frame)) - oanim["marker_names"].append(m.name) - - def process_bone(self, bone): - self.bobjectBoneArray[bone] = { - "objectType": NodeTypeBone, - "structName": bone.name, - } - for subbobject in bone.children: - self.process_bone(subbobject) - - def process_bobject(self, bobject): - if bobject.type not in ["MESH", "ARMATURE"]: - return - - btype = NodeTypeMesh if bobject.type == "MESH" else 0 - self.bobjectArray[bobject] = {"objectType": btype, "structName": bobject.name} - - if bobject.type == "ARMATURE": - if skeleton := bobject.data: - for bone in skeleton.bones: - if not bone.parent: - self.process_bone(bone) - - for subbobject in bobject.children: - self.process_bobject(subbobject) - - def process_skinned_meshes(self): - for bobjectRef in self.bobjectArray.items(): - if bobjectRef[1]["objectType"] == NodeTypeMesh: - if armature := bobjectRef[0].find_armature(): - for bone in armature.data.bones: - boneRef = self.find_bone(bone.name) - if boneRef: - boneRef[1]["objectType"] = NodeTypeBone - - def export_bone_transform(self, armature, bone, o, action): - pose_bone = armature.pose.bones.get(bone.name) - transform = bone.matrix_local.copy() - if bone.parent is not None: - transform = bone.parent.matrix_local.inverted_safe() @ transform - - o["transform"] = {} - o["transform"]["values"] = self.write_matrix(transform) - - curve_array = self.collect_bone_animation(armature, bone.name) - animation = len(curve_array) != 0 - - if animation and pose_bone: - begin_frame = int(action.frame_range[0]) - end_frame = int(action.frame_range[1]) - tracko = {} - o["anim"] = {"tracks": [tracko]} - tracko["target"] = "transform" - tracko["frames"] = [ - i - begin_frame for i in range(begin_frame, end_frame + 1) - ] - tracko["values"] = [] - self.bone_tracks.append((tracko["values"], pose_bone)) - - def write_bone_matrices(self, scene, action): - if len(self.bone_tracks) > 0: - begin_frame = int(action.frame_range[0]) - end_frame = int(action.frame_range[1]) - for i in range(begin_frame, end_frame + 1): - scene.frame_set(i) - for track in self.bone_tracks: - values, pose_bone = track[0], track[1] - if parent := pose_bone.parent: - values += self.write_matrix( - (parent.matrix.inverted_safe() @ pose_bone.matrix) - ) - else: - values += self.write_matrix(pose_bone.matrix) - - def export_object(self, bobject, scene, parento=None): - if bobjectRef := self.bobjectArray.get(bobject): - o = { - "type": structIdentifier[bobjectRef["objectType"]], - "name": bobjectRef["structName"], - } - if bobject.parent_type == "BONE": - o["parent_bone"] = bobject.parent_bone - - if bobjectRef["objectType"] == NodeTypeMesh: - objref = bobject.data - if objref not in self.meshArray: - self.meshArray[objref] = { - "structName": objref.name, - "objectTable": [bobject], - } - else: - self.meshArray[objref]["objectTable"].append(bobject) - oid = self.meshArray[objref]["structName"] - o["data_ref"] = oid - o["dimensions"] = self.calc_aabb(bobject) - - o["transform"] = {} - o["transform"]["values"] = self.write_matrix(bobject.matrix_local) - - # If the object is parented to a bone and is not relative, undo the - # bone's transform - if bobject.parent_type == "BONE": - armature = bobject.parent.data - bone = armature.bones[bobject.parent_bone] - o["parent_bone_connected"] = bone.use_connect - if bone.use_connect: - bone_translation = Vector((0, bone.length, 0)) + bone.head - o["parent_bone_tail"] = [ - bone_translation[0], - bone_translation[1], - bone_translation[2], - ] - else: - bone_translation = bone.tail - bone.head - o["parent_bone_tail"] = [ - bone_translation[0], - bone_translation[1], - bone_translation[2], - ] - pose_bone = bobject.parent.pose.bones[bobject.parent_bone] - bone_translation_pose = pose_bone.tail - pose_bone.head - o["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: - bdata = bobject.data - action = None - adata = bobject.animation_data - - # Active action - if adata is not None: - action = adata.action - if action is None: - bobject.animation_data_create() - actions = bpy.data.actions - action = actions.get("leenkx_pose") - if action is None: - action = actions.new(name="leenkx_pose") - - # Collect export actions - export_actions = [action] - if hasattr(adata, "nla_tracks") and adata.nla_tracks is not None: - for track in adata.nla_tracks: - if track.strips is None: - continue - for strip in track.strips: - if strip.action is None: - continue - if strip.action.name == action.name: - continue - export_actions.append(strip.action) - - basename = os.path.basename(self.filepath)[:-4] - o["bone_actions"] = [] - for action in export_actions: - o["bone_actions"].append(basename + "_" + action.name) - - orig_action = bobject.animation_data.action - for action in export_actions: - bobject.animation_data.action = action - bones = [] - self.bone_tracks = [] - for bone in bdata.bones: - if not bone.parent: - boneo = {} - self.export_bone(bobject, bone, scene, boneo, action) - bones.append(boneo) - self.write_bone_matrices(scene, action) - if len(bones) > 0 and "anim" in bones[0]: - self.export_pose_markers(bones[0]["anim"], action) - # Save action separately - action_obj = {} - action_obj["name"] = action.name - action_obj["objects"] = bones - self.write_lnx( - self.filepath[:-4] + "_" + action.name + ".lnx", action_obj - ) - - bobject.animation_data.action = orig_action - - if parento is None: - self.output["objects"].append(o) - else: - parento["children"].append(o) - - if not hasattr(o, "children") and len(bobject.children) > 0: - o["children"] = [] - - for subbobject in bobject.children: - self.export_object(subbobject, scene, o) - - def export_skin(self, bobject, armature, exportMesh, o): - # This function exports all skinning data, which includes the skeleton - # and per-vertex bone influence data - oskin = {} - o["skin"] = oskin - - # Write the skin bind pose transform - otrans = {} - oskin["transform"] = otrans - otrans["values"] = self.write_matrix(bobject.matrix_world) - - bone_array = armature.data.bones - bone_count = len(bone_array) - max_bones = 128 - bone_count = min(bone_count, max_bones) - - # Write the bone object reference array - oskin["bone_ref_array"] = np.empty(bone_count, dtype=object) - oskin["bone_len_array"] = np.empty(bone_count, dtype="= 0: # and bone_weight != 0.0: - bone_values.append((bone_weight, bone_index)) - total_weight += bone_weight - bone_count += 1 - - if bone_count > 4: - bone_count = 4 - bone_values.sort(reverse=True) - bone_values = bone_values[:4] - - bone_count_array[index] = bone_count - for bv in bone_values: - bone_weight_array[count] = bv[0] - bone_index_array[count] = bv[1] - count += 1 - - if total_weight not in (0.0, 1.0): - normalizer = 1.0 / total_weight - for i in range(bone_count): - bone_weight_array[count - i - 1] *= normalizer - - bone_index_array = bone_index_array[:count] - bone_weight_array = bone_weight_array[:count] - bone_weight_array *= 32767 - bone_weight_array = np.array(bone_weight_array, dtype=" 0 - has_tex1 = num_uv_layers > 1 - has_col = num_colors > 0 - has_tang = False - - pdata = np.empty(num_verts * 4, dtype=" maxdim: - maxdim = abs(v.uv[0]) - if abs(v.uv[1]) > maxdim: - maxdim = abs(v.uv[1]) - if has_tex1: - lay1 = uv_layers[t1map] - for v in lay1.data: - if abs(v.uv[0]) > maxdim: - maxdim = abs(v.uv[0]) - if abs(v.uv[1]) > maxdim: - maxdim = abs(v.uv[1]) - if maxdim > 1: - o["scale_tex"] = maxdim - invscale_tex = (1 / o["scale_tex"]) * 32767 - else: - invscale_tex = 1 * 32767 - if has_tang: - exportMesh.calc_tangents(uvmap=lay0.name) - tangdata = np.empty(num_verts * 3, dtype=" 2: - o["scale_pos"] = maxdim / 2 - else: - o["scale_pos"] = 1.0 - if has_armature: # Allow up to 2x bigger bounds for skinned mesh - o["scale_pos"] *= 2.0 - - scale_pos = o["scale_pos"] - invscale_pos = (1 / scale_pos) * 32767 - - verts = exportMesh.vertices - if has_tex: - lay0 = exportMesh.uv_layers[t0map] - if has_tex1: - lay1 = exportMesh.uv_layers[t1map] - if has_col: - vcol0 = exportMesh.vertex_colors[0].data - - for i, loop in enumerate(loops): - v = verts[loop.vertex_index] - co = v.co - normal = loop.normal - tang = loop.tangent - - i4 = i * 4 - i2 = i * 2 - pdata[i4] = co[0] - pdata[i4 + 1] = co[1] - pdata[i4 + 2] = co[2] - pdata[i4 + 3] = normal[2] * scale_pos # Cancel scale - ndata[i2] = normal[0] - ndata[i2 + 1] = normal[1] - if has_tex: - uv = lay0.data[loop.index].uv - t0data[i2] = uv[0] - t0data[i2 + 1] = 1.0 - uv[1] # Reverse Y - if has_tex1: - uv = lay1.data[loop.index].uv - t1data[i2] = uv[0] - t1data[i2 + 1] = 1.0 - uv[1] - if has_tang: - i3 = i * 3 - tangdata[i3] = tang[0] - tangdata[i3 + 1] = tang[1] - tangdata[i3 + 2] = tang[2] - if has_col: - col = vcol0[loop.index].color - i3 = i * 3 - cdata[i3] = col[0] - cdata[i3 + 1] = col[1] - cdata[i3 + 2] = col[2] - - mats = exportMesh.materials - poly_map = [] - for i in range(max(len(mats), 1)): - poly_map.append([]) - for poly in exportMesh.polygons: - poly_map[poly.material_index].append(poly) - - o["index_arrays"] = [] - - # map polygon indices to triangle loops - tri_loops = {} - for loop in exportMesh.loop_triangles: - if loop.polygon_index not in tri_loops: - tri_loops[loop.polygon_index] = [] - tri_loops[loop.polygon_index].append(loop) - - for index, polys in enumerate(poly_map): - tris = 0 - for poly in polys: - tris += poly.loop_total - 2 - if tris == 0: # No face assigned - continue - prim = np.empty(tris * 3, dtype=" 1: - for i in range(len(mats)): # Multi-mat mesh - if mats[i] == mats[index]: # Default material for empty slots - ia["material"] = i - break - o["index_arrays"].append(ia) - - # Pack - pdata *= invscale_pos - ndata *= 32767 - pdata = np.array(pdata, dtype="= -32: - fp.write(struct.pack("b", obj)) - elif obj >= -(2 ** (8 - 1)): - fp.write(b"\xd0" + struct.pack("b", obj)) - elif obj >= -(2 ** (16 - 1)): - fp.write(b"\xd1" + struct.pack("= -(2 ** (32 - 1)): - fp.write(b"\xd2" + struct.pack("= -(2 ** (64 - 1)): - fp.write(b"\xd3" + struct.pack(" 0 and isinstance(obj[0], float): - fp.write(b"\xca") - for e in obj: - fp.write(struct.pack(" 0 and isinstance(obj[0], bool): - for e in obj: - pack(e, fp) - elif len(obj) > 0 and isinstance(obj[0], int): - fp.write(b"\xd2") - for e in obj: - fp.write(struct.pack(" 0 and isinstance(obj[0], np.float32): - fp.write(b"\xca") - fp.write(obj.tobytes()) - # Int32 - elif len(obj) > 0 and isinstance(obj[0], np.int32): - fp.write(b"\xd2") - fp.write(obj.tobytes()) - # Int16 - elif len(obj) > 0 and isinstance(obj[0], np.int16): - fp.write(b"\xd1") - fp.write(obj.tobytes()) - # Regular - else: - for e in obj: - pack(e, fp) - - -def _pack_map(obj, fp): - if len(obj) <= 15: - fp.write(struct.pack("B", 0x80 | len(obj))) - elif len(obj) <= 2**16 - 1: - fp.write(b"\xde" + struct.pack("