From a83c74ebf7f02dd2e90fb19ec2ae86206f33c996 Mon Sep 17 00:00:00 2001 From: Will Snyder Date: Sun, 29 Nov 2020 15:45:32 -0500 Subject: [PATCH 1/2] Better anim abstraction --- addons/io_scene_swbf_msh/msh_anim_gather.py | 28 +++++++++++---------- addons/io_scene_swbf_msh/msh_model.py | 18 +++++++++++-- addons/io_scene_swbf_msh/msh_scene_save.py | 26 +++++++++---------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/addons/io_scene_swbf_msh/msh_anim_gather.py b/addons/io_scene_swbf_msh/msh_anim_gather.py index 1da0003..f959a19 100644 --- a/addons/io_scene_swbf_msh/msh_anim_gather.py +++ b/addons/io_scene_swbf_msh/msh_anim_gather.py @@ -27,19 +27,23 @@ def extract_anim(armature: bpy.types.Armature) -> Animation: anim.end_index = num_frames - 1 - anim.bone_transforms["DummyRoot"] = [] + anim.bone_frames["DummyRoot"] = ([], []) for bone in armature.data.bones: - anim.bone_transforms[bone.name] = [] + anim.bone_frames[bone.name] = ([], []) for frame in range(num_frames): - - #if frame % 10 == 0: - # print("Sample frame {}:".format(frame)) - + frame_time = framerange.x + frame * increment bpy.context.scene.frame_set(frame_time) - anim.bone_transforms["DummyRoot"].append(ModelTransform()) + + rframe_dummy = RotationFrame(frame, convert_rotation_space(Quaternion())) + tframe_dummy = TranslationFrame(frame, Vector((0.0,0.0,0.0))) + + anim.bone_frames["DummyRoot"][0].append(tframe_dummy) + anim.bone_frames["DummyRoot"][1].append(rframe_dummy) + + for bone in armature.pose.bones: transform = bone.matrix @@ -49,13 +53,11 @@ def extract_anim(armature: bpy.types.Armature) -> Animation: loc, rot, _ = transform.decompose() - xform = ModelTransform() - xform.rotation = convert_rotation_space(rot) - xform.translation = convert_vector_space(loc) + rframe = RotationFrame(frame, convert_rotation_space(rot)) + tframe = TranslationFrame(frame, convert_vector_space(loc)) - #if frame % 10 == 0: - # print("\t{:10}: loc {:15} rot {:15}".format(bone.name, vec_to_str(xform.translation), quat_to_str(xform.rotation))) + anim.bone_frames[bone.name][0].append(tframe) + anim.bone_frames[bone.name][1].append(rframe) - anim.bone_transforms[bone.name].append(xform) return anim diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index fbf5dc8..af75efd 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -76,14 +76,28 @@ class Model: geometry: List[GeometrySegment] = None collisionprimitive: CollisionPrimitive = None + +@dataclass +class RotationFrame: + + index : int = 0 + rotation : Quaternion = field(default_factory=Quaternion) + + +@dataclass +class TranslationFrame: + + index : int = 0 + translation : Vector = field(default_factory=Vector) + + @dataclass class Animation: """ Class representing 'CYCL' + 'KFR3' sections in a .msh file """ name: str = "fullanimation" - bone_transforms: Dict[str, List[ModelTransform]] = field(default_factory=dict) + bone_frames: Dict[str, Tuple[List[TranslationFrame], List[RotationFrame]]] = field(default_factory=dict) framerate: float = 29.97 start_index : int = 0 end_index : int = 0 - diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index 6507b17..ed7a75b 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -248,14 +248,14 @@ def _write_envl(envl: Writer, model: Model, model_index: Dict[str, int]): SKELETON CHUNKS ''' def _write_bln2(bln2: Writer, scene: Scene): - bones = scene.anims[0].bone_transforms.keys() + bones = scene.anims[0].bone_frames.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() + bones = scene.anims[0].bone_frames.keys() skl2.write_u32(len(bones)) for boneName in bones: @@ -282,22 +282,20 @@ def _write_anm2(anm2: Writer, anim: Animation): with anm2.create_child("KFR3") as kfr3: - kfr3.write_u32(len(anim.bone_transforms.keys())) + kfr3.write_u32(len(anim.bone_frames)) - for boneName in anim.bone_transforms.keys(): + for boneName in anim.bone_frames: kfr3.write_u32(crc(boneName)) kfr3.write_u32(0) #what is keyframe type? - 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) - kfr3.write_f32(xform.translation.x, xform.translation.y, xform.translation.z) - - for i, xform in enumerate(anim.bone_transforms[boneName]): - kfr3.write_u32(i) - kfr3.write_f32(xform.rotation.x, xform.rotation.y, xform.rotation.z, xform.rotation.w) + translation_frames, rotation_frames = anim.bone_frames[boneName] + kfr3.write_u32(len(translation_frames), len(rotation_frames)) + for frame in translation_frames: + kfr3.write_u32(frame.index) + kfr3.write_f32(frame.translation.x, frame.translation.y, frame.translation.z) + for frame in rotation_frames: + kfr3.write_u32(frame.index) + kfr3.write_f32(frame.rotation.x, frame.rotation.y, frame.rotation.z, frame.rotation.w) From 617118bfd80291392185246ba086d28088295d58 Mon Sep 17 00:00:00 2001 From: Will Snyder Date: Sun, 29 Nov 2020 20:10:16 -0500 Subject: [PATCH 2/2] Name agnostic armature handling, better export options --- addons/io_scene_swbf_msh/__init__.py | 16 +++++------ addons/io_scene_swbf_msh/msh_anim_gather.py | 8 +++--- addons/io_scene_swbf_msh/msh_model_gather.py | 22 ++++----------- addons/io_scene_swbf_msh/msh_scene.py | 17 ++++++++---- addons/io_scene_swbf_msh/msh_scene_save.py | 29 ++++++++++---------- 5 files changed, 43 insertions(+), 49 deletions(-) diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index 8b97f68..69edf37 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -97,15 +97,15 @@ class ExportMSH(Operator, ExportHelper): default=True ) - export_animated: BoolProperty( - name="Export Animated Object", - description="Always check if the object will be animated.", + export_with_animation: BoolProperty( + name="Export With Animation", + description="Includes animation data extracted from the action currently set on armature.", default=False ) - export_skeleton_only: BoolProperty( - name="Export Skeleton", - description="Check if you intend to export skeleton data only.", + export_as_skeleton: BoolProperty( + name="Export Objects As Skeleton", + description="Check if you intend to export skeleton data for consumption by ZenAsset.", default=False ) @@ -119,9 +119,9 @@ class ExportMSH(Operator, ExportHelper): generate_triangle_strips=self.generate_triangle_strips, apply_modifiers=self.apply_modifiers, export_target=self.export_target, - skel_only=self.export_skeleton_only + skel_only=self.export_as_skeleton, + export_anim=self.export_with_animation ), - is_animated=self.export_animated ) return {'FINISHED'} diff --git a/addons/io_scene_swbf_msh/msh_anim_gather.py b/addons/io_scene_swbf_msh/msh_anim_gather.py index f959a19..0129587 100644 --- a/addons/io_scene_swbf_msh/msh_anim_gather.py +++ b/addons/io_scene_swbf_msh/msh_anim_gather.py @@ -12,7 +12,7 @@ from .msh_utilities import * from .msh_model_gather import * -def extract_anim(armature: bpy.types.Armature) -> Animation: +def extract_anim(armature: bpy.types.Armature, root_name: str) -> Animation: action = armature.animation_data.action anim = Animation(); @@ -27,7 +27,7 @@ def extract_anim(armature: bpy.types.Armature) -> Animation: anim.end_index = num_frames - 1 - anim.bone_frames["DummyRoot"] = ([], []) + anim.bone_frames[root_name] = ([], []) for bone in armature.data.bones: anim.bone_frames[bone.name] = ([], []) @@ -40,8 +40,8 @@ def extract_anim(armature: bpy.types.Armature) -> Animation: rframe_dummy = RotationFrame(frame, convert_rotation_space(Quaternion())) tframe_dummy = TranslationFrame(frame, Vector((0.0,0.0,0.0))) - anim.bone_frames["DummyRoot"][0].append(tframe_dummy) - anim.bone_frames["DummyRoot"][1].append(rframe_dummy) + anim.bone_frames[root_name][0].append(tframe_dummy) + anim.bone_frames[root_name][1].append(rframe_dummy) for bone in armature.pose.bones: diff --git a/addons/io_scene_swbf_msh/msh_model_gather.py b/addons/io_scene_swbf_msh/msh_model_gather.py index ea1fe03..b0a6df8 100644 --- a/addons/io_scene_swbf_msh/msh_model_gather.py +++ b/addons/io_scene_swbf_msh/msh_model_gather.py @@ -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, skeleton_only: bool) -> List[Model]: +def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool) -> Tuple[List[Model], bpy.types.Object]: """ Gathers the Blender objects from the current scene and returns them as a list of Model objects. """ @@ -23,6 +23,8 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool models_list: List[Model] = [] + armature_found = None + for uneval_obj in select_objects(export_target): if uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents: continue @@ -36,6 +38,7 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool if obj.type == "ARMATURE": models_list += expand_armature(obj) + armature_found = obj continue model = Model() @@ -44,7 +47,6 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool model.hidden = get_is_model_hidden(obj) transform = obj.matrix_local - transform_reset = Matrix.Identity(4) if obj.parent_bone: model.parent = obj.parent_bone @@ -52,18 +54,8 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool # matrix_local, when called on an armature child also parented to a bone, appears to be broken. # At the very least, the results contradict the docs... armature_relative_transform = obj.parent.matrix_world.inverted() @ obj.matrix_world - bone_relative_transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ armature_relative_transform + transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ armature_relative_transform - transform = bone_relative_transform - - ''' - # Since the transforms of direct bone children are discarded by ZEngine (but not ZEditor), we apply the transform - # before geometry extraction, then apply the inversion after. - if obj.type in MESH_OBJECT_TYPES: - obj.data.transform(bone_relative_transform) - transform_reset = bone_relative_transform.inverted() - transform = Matrix.Identity(4) - ''' else: if obj.parent is not None: if obj.parent.type == "ARMATURE": @@ -94,8 +86,6 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool if obj.vertex_groups: model.bone_map = [group.name for group in obj.vertex_groups] - obj.data.transform(transform_reset) - if get_is_collision_primitive(obj): model.collisionprimitive = get_collision_primitive(obj) @@ -104,7 +94,7 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool models_list.append(model) - return models_list + return (models_list, armature_found) def create_parents_set() -> Set[str]: diff --git a/addons/io_scene_swbf_msh/msh_scene.py b/addons/io_scene_swbf_msh/msh_scene.py index 2f37f9a..090a3f4 100644 --- a/addons/io_scene_swbf_msh/msh_scene.py +++ b/addons/io_scene_swbf_msh/msh_scene.py @@ -44,10 +44,10 @@ class Scene: name: str = "Scene" materials: Dict[str, Material] = field(default_factory=dict) models: List[Model] = field(default_factory=list) - anims: List[Animation] = field(default_factory=list) + animation: Animation = None -def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str, skel_only: bool) -> Scene: +def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str, skel_only: bool, export_anim: 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, skeleton_only=skel_only) + scene.models, armature_obj = 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: @@ -72,12 +72,17 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t scene.materials = remove_unused_materials(scene.materials, scene.models) - #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"])] root = scene.models[0] + + if export_anim: + if armature_obj is not None: + scene.animation = extract_anim(armature_obj, root.name) + else: + raise Exception("Export Error: Could not find an armature object from which to export an animation!") + if skel_only and root.model_type == ModelType.NULL: + # For ZenAsset inject_dummy_data(root) return scene diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index ed7a75b..f35e5c9 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -10,7 +10,7 @@ from .msh_utilities import * from .crc import * -def save_scene(output_file, scene: Scene, is_animated: bool): +def save_scene(output_file, scene: Scene): """ Saves scene to the supplied file. """ with Writer(file=output_file, chunk_id="HEDR") as hedr: @@ -32,16 +32,15 @@ def save_scene(output_file, scene: Scene, is_animated: bool): with msh2.create_child("MODL") as modl: _write_modl(modl, model, index, material_index, model_index) - if is_animated: + if scene.animation is not None: with hedr.create_child("SKL2") as skl2: - _write_skl2(skl2, scene) + _write_skl2(skl2, scene.animation) with hedr.create_child("BLN2") as bln2: - _write_bln2(bln2, scene) + _write_bln2(bln2, scene.animation) - with hedr.create_child("ANM2") as anm2: #simple for now - for anim in scene.anims: - _write_anm2(anm2, anim) + with hedr.create_child("ANM2") as anm2: #simple for now + _write_anm2(anm2, scene.animation) with hedr.create_child("CL1L"): pass @@ -247,23 +246,23 @@ def _write_envl(envl: Writer, model: Model, model_index: Dict[str, int]): ''' SKELETON CHUNKS ''' -def _write_bln2(bln2: Writer, scene: Scene): - bones = scene.anims[0].bone_frames.keys() +def _write_bln2(bln2: Writer, anim: Animation): + bones = anim.bone_frames.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_frames.keys() - skl2.write_u32(len(bones)) +def _write_skl2(skl2: Writer, anim: Animation): + bones = anim.bone_frames.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 + skl2.write_u32(crc(boneName), 0) #default values from docs + skl2.write_f32(1.0, 0.0, 0.0) ''' -ANIMATION CHUNK +ANIMATION CHUNKS ''' def _write_anm2(anm2: Writer, anim: Animation):