Working for complex skinned exports, still crashes ZE, menus need work
This commit is contained in:
		| @@ -97,9 +97,15 @@ class ExportMSH(Operator, ExportHelper): | ||||
|         default=True | ||||
|     ) | ||||
|  | ||||
|     export_anims: BoolProperty( | ||||
|         name="Export Animations", | ||||
|         description="Action export test", | ||||
|     export_animated: BoolProperty( | ||||
|         name="Export Animated Object", | ||||
|         description="Always check if the object will be animated.", | ||||
|         default=False | ||||
|     ) | ||||
|  | ||||
|     export_skeleton_only: BoolProperty( | ||||
|         name="Export Skeleton", | ||||
|         description="Check if you intend to export skeleton data only.", | ||||
|         default=False | ||||
|     ) | ||||
|  | ||||
| @@ -112,8 +118,10 @@ class ExportMSH(Operator, ExportHelper): | ||||
|                 scene=create_scene( | ||||
|                     generate_triangle_strips=self.generate_triangle_strips,  | ||||
|                     apply_modifiers=self.apply_modifiers, | ||||
|                     export_target=self.export_target), | ||||
|                 separate_anims=self.export_anims | ||||
|                     export_target=self.export_target, | ||||
|                     skel_only=self.export_skeleton_only | ||||
|                 ), | ||||
|                 is_animated=self.export_animated | ||||
|             ) | ||||
|  | ||||
|         return {'FINISHED'} | ||||
|   | ||||
| @@ -12,65 +12,42 @@ from .msh_utilities import * | ||||
| from .msh_model_gather import * | ||||
|  | ||||
|  | ||||
| def gather_animdata(armature: bpy.types.Armature) -> List[Animation]: | ||||
|  | ||||
|     anim_data = Animation(); | ||||
| def extract_anim(armature: bpy.types.Armature) -> Animation: | ||||
|  | ||||
|     action = armature.animation_data.action | ||||
|     anim = Animation(); | ||||
|  | ||||
|     if not action: | ||||
|         framerange = Vector((0.0,1.0)) | ||||
|     else: | ||||
|         framerange = action.frame_range | ||||
|      | ||||
|     framerange = action.frame_range | ||||
|     increment  = (framerange.y - framerange.x) / 20.0 | ||||
|     offset     = framerange.x; | ||||
|     num_frames = math.floor(framerange.y - framerange.x) + 1 | ||||
|     increment  = (framerange.y - framerange.x) / (num_frames - 1) | ||||
|  | ||||
|     anim_data.bone_transforms[armature.parent.name] = [] | ||||
|     anim.end_index = num_frames - 1 | ||||
|      | ||||
|     for bone in armature.data.bones: | ||||
|         anim_data.bone_transforms[bone.name] = [] | ||||
|         anim.bone_transforms[bone.name] = [] | ||||
|  | ||||
|     for frame in range(21): | ||||
|         frame_time = offset + frame * increment | ||||
|     for frame in range(num_frames): | ||||
|          | ||||
|         frame_time = framerange.x + frame * increment | ||||
|         bpy.context.scene.frame_set(frame_time) | ||||
|  | ||||
|         anim_data.bone_transforms[armature.parent.name].append(ModelTransform()) #for testing | ||||
|  | ||||
|         for bone in armature.pose.bones: | ||||
|  | ||||
|             xform = ModelTransform() | ||||
|             xform.translation = to_skeleton_vector_space(bone.location) | ||||
|             xform.rotation = to_skeleton_rotation_space(bone.rotation_quaternion) | ||||
|             		 | ||||
|             anim_data.bone_transforms[bone.name].append(xform) | ||||
|              | ||||
|              | ||||
|     return [anim_data] | ||||
|             transform = bone.matrix | ||||
|  | ||||
|  | ||||
| def get_basepose(armature: bpy.types.Armature) -> Animation: | ||||
|  | ||||
|     anim_data = Animation(); | ||||
|     anim_data.name = "basepose" | ||||
|  | ||||
|     bpy.context.scene.frame_set(0.0) | ||||
|  | ||||
|     anim_data.bone_transforms[armature.parent.name] = [] | ||||
|     for bone in armature.data.bones: | ||||
|         anim_data.bone_transforms[bone.name] = [] | ||||
|  | ||||
|     for frame in range(2): | ||||
|  | ||||
|         anim_data.bone_transforms[armature.parent.name].append(ModelTransform()) #for testing | ||||
|  | ||||
|         for bone in armature.pose.bones: | ||||
|             if bone.parent: | ||||
|                 transform = bone.parent.matrix.inverted() @ transform | ||||
|   | ||||
|             loc, rot, _ = transform.decompose() | ||||
|  | ||||
|             xform = ModelTransform() | ||||
|             xform.translation = to_skeleton_vector_space(bone.location) | ||||
|             xform.rotation = to_skeleton_rotation_space(bone.rotation_quaternion) | ||||
|             		 | ||||
|             anim_data.bone_transforms[bone.name].append(xform) | ||||
|              | ||||
|              | ||||
|     return anim_data | ||||
|  | ||||
|  | ||||
|  | ||||
|             xform.rotation = convert_rotation_space(rot) | ||||
|             xform.translation = convert_vector_space(loc) | ||||
|  | ||||
|             anim.bone_transforms[bone.name].append(xform) | ||||
|  | ||||
|     return anim | ||||
|   | ||||
| @@ -80,6 +80,10 @@ class Model: | ||||
| class Animation: | ||||
|     """ Class representing 'CYCL' + 'KFR3' sections in a .msh file """ | ||||
|  | ||||
|     name: str = "wiggle" | ||||
|     name: str = "fullanimation" | ||||
|     bone_transforms: Dict[str, List[ModelTransform]] = field(default_factory=dict) | ||||
|  | ||||
|     framerate: float = 10.0 | ||||
|     start_index : int = 0 | ||||
|     end_index   : int = 0 | ||||
|      | ||||
|   | ||||
| @@ -14,7 +14,7 @@ SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE"} | ||||
| MESH_OBJECT_TYPES = {"MESH", "CURVE", "SURFACE", "META", "FONT", "GPENCIL"} | ||||
| MAX_MSH_VERTEX_COUNT = 32767 | ||||
|  | ||||
| def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: | ||||
| def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool) -> List[Model]: | ||||
|     """ Gathers the Blender objects from the current scene and returns them as a list of | ||||
|         Model objects. """ | ||||
|  | ||||
| @@ -36,15 +36,26 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: | ||||
|  | ||||
|         if obj.type == "ARMATURE": | ||||
|             models_list += expand_armature(obj) | ||||
|             continue | ||||
|  | ||||
|         local_translation, local_rotation, _ = obj.matrix_local.decompose() | ||||
|         if skeleton_only: | ||||
|             continue | ||||
|  | ||||
|         model = Model() | ||||
|         model.name = obj.name | ||||
|         model.model_type = get_model_type(obj) | ||||
|         model.hidden = get_is_model_hidden(obj) | ||||
|         model.transform.rotation = convert_rotation_space(local_rotation) | ||||
|  | ||||
|         transform = obj.matrix_local | ||||
|  | ||||
|         if obj.parent_bone: | ||||
|             model.parent = obj.parent_bone | ||||
|             transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ transform | ||||
|         else: | ||||
|             if obj.parent is not None: | ||||
|                 model.parent = obj.parent.name | ||||
|  | ||||
|         local_translation, local_rotation, _ = transform.decompose() | ||||
|         model.transform.rotation = convert_rotation_space(local_rotation)   | ||||
|         model.transform.translation = convert_vector_space(local_translation) | ||||
|  | ||||
|         if obj.type in MESH_OBJECT_TYPES: | ||||
| @@ -126,11 +137,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet | ||||
|  | ||||
|         if use_smooth_normal or mesh.use_auto_smooth: | ||||
|             if mesh.has_custom_normals: | ||||
|                 vertex_normal = mesh.loops[loop_index].normal | ||||
|                 vertex_normal = Vector( mesh.loops[loop_index].normal ) | ||||
|             else: | ||||
|                 vertex_normal = mesh.vertices[vertex_index].normal | ||||
|                 vertex_normal = Vector( mesh.vertices[vertex_index].normal ) | ||||
|         else: | ||||
|             vertex_normal = face_normal | ||||
|             vertex_normal = Vector(face_normal) | ||||
|  | ||||
|         def get_cache_vertex(): | ||||
|             yield mesh.vertices[vertex_index].co.x | ||||
| @@ -343,6 +354,8 @@ def select_objects(export_target: str) -> List[bpy.types.Object]: | ||||
|  | ||||
|     return objects + parents | ||||
|  | ||||
|  | ||||
|  | ||||
| def expand_armature(obj: bpy.types.Object) -> List[Model]: | ||||
|     bones: List[Model] = [] | ||||
|  | ||||
| @@ -355,7 +368,7 @@ def expand_armature(obj: bpy.types.Object) -> List[Model]: | ||||
|             transform = bone.parent.matrix_local.inverted() @ transform | ||||
|             model.parent = bone.parent.name | ||||
|         else: | ||||
|             model.parent = "DummyRoot" # obj.name | ||||
|             model.parent = obj.name | ||||
|  | ||||
|         local_translation, local_rotation, _ = transform.decompose() | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| from typing import List | ||||
| from .msh_model import * | ||||
| from .msh_utilities import * | ||||
| import mathutils | ||||
| import math | ||||
| from mathutils import Vector, Matrix | ||||
|  | ||||
| def scale_segments(scale: Vector, segments: List[GeometrySegment]): | ||||
| @@ -125,15 +127,3 @@ def convert_scale_space(vec: Vector) -> Vector: | ||||
| def convert_rotation_space(quat: Quaternion) -> Quaternion: | ||||
|     return Quaternion((-quat.w, quat.x, -quat.z, -quat.y)) | ||||
|  | ||||
|  | ||||
| def to_skeleton_vector_space(vec : Vector): | ||||
|     vnew = convert_vector_space(vec) | ||||
|     vnew.x *= -1.0 | ||||
|     return vnew | ||||
|  | ||||
| def to_skeleton_rotation_space(quat : Quaternion): | ||||
|     qnew = convert_rotation_space(quat) | ||||
|     qnew.x *= -1.0 | ||||
|     qnew.y *= -1.0 | ||||
|     qnew.z *= -1.0 | ||||
|     return qnew | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class Scene: | ||||
|     anims:  List[Animation] = field(default_factory=list) | ||||
|  | ||||
|  | ||||
| def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str) -> Scene: | ||||
| def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str, skel_only: bool) -> Scene: | ||||
|     """ Create a msh Scene from the active Blender scene. """ | ||||
|  | ||||
|     scene = Scene() | ||||
| @@ -56,7 +56,7 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t | ||||
|  | ||||
|     scene.materials = gather_materials() | ||||
|  | ||||
|     scene.models = gather_models(apply_modifiers=apply_modifiers, export_target=export_target) | ||||
|     scene.models = gather_models(apply_modifiers=apply_modifiers, export_target=export_target, skeleton_only=skel_only) | ||||
|     scene.models = sort_by_parent(scene.models) | ||||
|  | ||||
|     if generate_triangle_strips: | ||||
| @@ -70,13 +70,15 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t | ||||
|     if has_multiple_root_models(scene.models): | ||||
|         scene.models = reparent_model_roots(scene.models) | ||||
|  | ||||
|  | ||||
|     scene.materials = remove_unused_materials(scene.materials, scene.models) | ||||
|  | ||||
|     scene.anims = gather_animdata(bpy.context.scene.objects["Armature"]) | ||||
|   | ||||
|     #creates a dummy basepose if no Action is selected | ||||
|     if "Armature" in bpy.context.scene.objects.keys(): | ||||
|         scene.anims = [extract_anim(bpy.context.scene.objects["Armature"])] | ||||
|  | ||||
|     return scene | ||||
|  | ||||
|  | ||||
| def create_scene_aabb(scene: Scene) -> SceneAABB: | ||||
|     """ Create a SceneAABB for a Scene. """ | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ from .msh_utilities import * | ||||
|  | ||||
| from .crc import * | ||||
|  | ||||
| def save_scene(output_file, scene: Scene, separate_anims: bool): | ||||
| def save_scene(output_file, scene: Scene, is_animated: bool): | ||||
|     """ Saves scene to the supplied file. """ | ||||
|  | ||||
|     with Writer(file=output_file, chunk_id="HEDR") as hedr: | ||||
| @@ -26,12 +26,19 @@ def save_scene(output_file, scene: Scene, separate_anims: bool): | ||||
|                 material_index = _write_matl_and_get_material_index(matl, scene) | ||||
|  | ||||
|             for index, model in enumerate(scene.models): | ||||
|                 if separate_anims and (model.model_type not in {ModelType.NULL, ModelType.BONE}): | ||||
|                     continue | ||||
|  | ||||
|                 print(model.name) | ||||
|  | ||||
|                 with msh2.create_child("MODL") as modl: | ||||
|                     _write_modl(modl, model, index, material_index, model_index) | ||||
|  | ||||
|         if separate_anims: | ||||
|         if is_animated: | ||||
|             with hedr.create_child("SKL2") as skl2: | ||||
|                 _write_skl2(skl2, scene) | ||||
|  | ||||
|             with hedr.create_child("BLN2") as bln2: | ||||
|                 _write_bln2(bln2, scene) | ||||
|  | ||||
|             with hedr.create_child("ANM2") as anm2: #simple for now | ||||
|                 for anim in scene.anims: | ||||
|                     _write_anm2(anm2, anim) | ||||
| @@ -44,8 +51,15 @@ def _write_sinf(sinf: Writer, scene: Scene): | ||||
|         name.write_string(scene.name) | ||||
|  | ||||
|     with sinf.create_child("FRAM") as fram: | ||||
|         fram.write_i32(0, 20) #test values | ||||
|         fram.write_f32(10.0) #test values | ||||
|         min_index = 0 | ||||
|         max_index = 1 | ||||
|  | ||||
|         if scene.anims and len(scene.anims) > 0: | ||||
|             min_index = min([anim.start_index for anim in scene.anims]) | ||||
|             max_index = min([anim.end_index for anim in scene.anims]) | ||||
|  | ||||
|         fram.write_i32(min_index, max_index) | ||||
|         fram.write_f32(10.0) | ||||
|  | ||||
|     with sinf.create_child("BBOX") as bbox: | ||||
|         aabb = create_scene_aabb(scene) | ||||
| @@ -130,6 +144,12 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str | ||||
|  | ||||
|     if model.geometry is not None: | ||||
|         with modl.create_child("GEOM") as geom: | ||||
|  | ||||
|             with geom.create_child("BBOX") as bbox: | ||||
|                 bbox.write_f32(0.0, 0.0, 0.0, 1.0) | ||||
|                 bbox.write_f32(0, 0, 0) | ||||
|                 bbox.write_f32(1.0,1.0,1.0,2.0) | ||||
|  | ||||
|             for segment in model.geometry: | ||||
|                 with geom.create_child("SEGM") as segm: | ||||
|                     _write_segm(segm, segment, material_index) | ||||
| @@ -168,7 +188,7 @@ def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str | ||||
|     with segm.create_child("NRML") as nrml: | ||||
|         nrml.write_u32(len(segment.normals)) | ||||
|  | ||||
|         for normal in segment.normals: | ||||
|         for i,normal in enumerate(segment.normals): | ||||
|             nrml.write_f32(normal.x, normal.y, normal.z) | ||||
|  | ||||
|     if segment.colors is not None: | ||||
| @@ -214,6 +234,8 @@ SKINNING CHUNKS | ||||
| def _write_wght(wght: Writer, weights: List[List[VertexWeight]]): | ||||
|     wght.write_u32(len(weights)) | ||||
|  | ||||
|     print("Writing WGHT: ") | ||||
|  | ||||
|     for weight_list in weights: | ||||
|         weight_list += [VertexWeight(0.0, 0)] * 4 | ||||
|         weight_list = sorted(weight_list, key=lambda w: w.weight, reverse=True) | ||||
| @@ -221,18 +243,46 @@ def _write_wght(wght: Writer, weights: List[List[VertexWeight]]): | ||||
|  | ||||
|         total_weight = max(sum(map(lambda w: w.weight, weight_list)), 1e-5) | ||||
|  | ||||
|         print_str = "" | ||||
|  | ||||
|         for weight in weight_list: | ||||
|     | ||||
|            print_str += "({}, {})  ".format(weight.bone, weight.weight / total_weight) | ||||
|  | ||||
|            wght.write_i32(weight.bone) | ||||
|            wght.write_f32(weight.weight / total_weight) | ||||
|  | ||||
|         print("  {}".format(print_str)) | ||||
|  | ||||
| def _write_envl(envl: Writer, model: Model, model_index: Dict[str, int]): | ||||
|     envl.write_u32(len(model.bone_map)) | ||||
|  | ||||
|     print("Writing ENVL: ") | ||||
|  | ||||
|     for bone_name in model.bone_map: | ||||
|         print("  {:10} Index: {}".format(bone_name, model_index[bone_name])) | ||||
|         envl.write_u32(model_index[bone_name]) | ||||
|  | ||||
| ''' | ||||
| ANIMATION CHUNKS | ||||
| SKELETON CHUNKS | ||||
| ''' | ||||
| def _write_bln2(bln2: Writer, scene: Scene): | ||||
|     bones = scene.anims[0].bone_transforms.keys() | ||||
|     bln2.write_u32(len(bones)) | ||||
|  | ||||
|     for boneName in bones: | ||||
|         bln2.write_u32(crc(boneName), 0)  | ||||
|  | ||||
| def _write_skl2(skl2: Writer, scene: Scene): | ||||
|     bones = scene.anims[0].bone_transforms.keys() | ||||
|     skl2.write_u32(len(bones)) | ||||
|  | ||||
|     for boneName in bones: | ||||
|         skl2.write_u32(crc(boneName), 0) #default values  | ||||
|         skl2.write_f32(1.0, 0.0, 0.0)   #from docs | ||||
|  | ||||
| ''' | ||||
| ANIMATION CHUNK | ||||
| ''' | ||||
| def _write_anm2(anm2: Writer, anim: Animation): | ||||
|  | ||||
| @@ -241,12 +291,12 @@ def _write_anm2(anm2: Writer, anim: Animation): | ||||
|         cycl.write_u32(1) | ||||
|         cycl.write_string(anim.name) | ||||
|          | ||||
|         for _ in range(63 - len(anim.name) ): | ||||
|         for _ in range(63 - len(anim.name)): | ||||
|             cycl.write_u8(0) | ||||
|          | ||||
|         cycl.write_f32(10.0) #test framerate | ||||
|         cycl.write_f32(anim.framerate) | ||||
|         cycl.write_u32(0) #what does play style refer to? | ||||
|         cycl.write_u32(0, 20) #first frame indices | ||||
|         cycl.write_u32(anim.start_index, anim.end_index) #first frame indices | ||||
|  | ||||
|  | ||||
|     with anm2.create_child("KFR3") as kfr3: | ||||
| @@ -257,7 +307,8 @@ def _write_anm2(anm2: Writer, anim: Animation): | ||||
|             kfr3.write_u32(crc(boneName)) | ||||
|             kfr3.write_u32(0) #what is keyframe type? | ||||
|  | ||||
|             kfr3.write_u32(21, 21) #basic testing | ||||
|             num_frames = 1 + anim.end_index - anim.start_index   | ||||
|             kfr3.write_u32(num_frames, num_frames) #basic testing | ||||
|  | ||||
|             for i, xform in enumerate(anim.bone_transforms[boneName]): | ||||
|                 kfr3.write_u32(i) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Will Snyder
					Will Snyder