diff --git a/io_scene_psk_psa/psa/builder.py b/io_scene_psk_psa/psa/builder.py index 95bd914..e52da43 100644 --- a/io_scene_psk_psa/psa/builder.py +++ b/io_scene_psk_psa/psa/builder.py @@ -1,7 +1,10 @@ -from bpy.types import Action, PoseBone +from bpy.types import Action, AnimData, Context, Object, PoseBone from .data import Psa -from ..shared.helpers import * +from typing import Dict, List, Optional, Tuple +from mathutils import Matrix, Quaternion, Vector + +from ..shared.helpers import create_psx_bones, get_coordinate_system_transform class PsaBuildSequence: @@ -62,7 +65,6 @@ def _get_pose_bone_location_and_rotation( # Get the bone's pose matrix and transform it into the export space. # In the case of an 'ARMATURE' export space, this will be the inverse of armature object's world matrix. # Otherwise, it will be the identity matrix. - # TODO: taking the pose bone matrix puts this in armature space. match export_space: case 'ARMATURE': pose_bone_matrix = pose_bone.matrix @@ -81,7 +83,7 @@ def _get_pose_bone_location_and_rotation( rotation = pose_bone_matrix.to_quaternion().normalized() # TODO: this has gotten way more complicated than it needs to be. - # TODO: don't apply scale to the root bone of armatures if we have a false root: + # Don't apply scale to the root bone of armatures if we have a false root. if not has_false_root_bone or (pose_bone is None or pose_bone.parent is not None): location *= scale @@ -96,7 +98,7 @@ def _get_pose_bone_location_and_rotation( return location, rotation -def build_psa(context: bpy.types.Context, options: PsaBuildOptions) -> Psa: +def build_psa(context: Context, options: PsaBuildOptions) -> Psa: psa = Psa() psx_bone_create_result = create_psx_bones( diff --git a/io_scene_psk_psa/psa/data.py b/io_scene_psk_psa/psa/data.py index 5dd82cc..0c1cd0b 100644 --- a/io_scene_psk_psa/psa/data.py +++ b/io_scene_psk_psa/psa/data.py @@ -2,15 +2,15 @@ import typing from collections import OrderedDict from typing import List -from ..shared.data import * - -''' -Note that keys are not stored within the Psa object. -Use the PsaReader::get_sequence_keys to get the keys for a sequence. -''' +from ctypes import Structure, c_char, c_int32, c_float +from ..shared.data import PsxBone, Quaternion, Vector3 class Psa: + ''' + Note that keys are not stored within the Psa object. + Use the PsaReader::get_sequence_keys to get the keys for a sequence. + ''' class Sequence(Structure): _fields_ = [ diff --git a/io_scene_psk_psa/psa/import_/operators.py b/io_scene_psk_psa/psa/import_/operators.py index 6649698..0169d91 100644 --- a/io_scene_psk_psa/psa/import_/operators.py +++ b/io_scene_psk_psa/psa/import_/operators.py @@ -2,13 +2,13 @@ import os from pathlib import Path from typing import Iterable -from bpy.props import StringProperty, CollectionProperty -from bpy.types import Operator, Event, Context, FileHandler, OperatorFileListElement, Object +from bpy.props import CollectionProperty, StringProperty +from bpy.types import Context, Event, FileHandler, Object, Operator, OperatorFileListElement from bpy_extras.io_utils import ImportHelper -from .properties import get_visible_sequences, PsaImportMixin +from .properties import PsaImportMixin, get_visible_sequences from ..config import read_psa_config -from ..importer import import_psa, PsaImportOptions +from ..importer import PsaImportOptions, import_psa from ..reader import PsaReader diff --git a/io_scene_psk_psa/psa/importer.py b/io_scene_psk_psa/psa/importer.py index 4855708..6bb6ec3 100644 --- a/io_scene_psk_psa/psa/importer.py +++ b/io_scene_psk_psa/psa/importer.py @@ -3,7 +3,7 @@ from typing import List, Optional import bpy import numpy as np -from bpy.types import FCurve, Object, Context +from bpy.types import Context, FCurve, Object from mathutils import Vector, Quaternion from .config import PsaConfig, REMOVE_TRACK_LOCATION, REMOVE_TRACK_ROTATION diff --git a/io_scene_psk_psa/psa/reader.py b/io_scene_psk_psa/psa/reader.py index c5ccf89..1869707 100644 --- a/io_scene_psk_psa/psa/reader.py +++ b/io_scene_psk_psa/psa/reader.py @@ -3,7 +3,8 @@ from typing import List import numpy as np -from .data import Psa, Section, PsxBone +from .data import Psa, PsxBone +from ..shared.data import Section def _try_fix_cue4parse_issue_103(sequences) -> bool: diff --git a/io_scene_psk_psa/psa/writer.py b/io_scene_psk_psa/psa/writer.py index 05001cb..015c1e5 100644 --- a/io_scene_psk_psa/psa/writer.py +++ b/io_scene_psk_psa/psa/writer.py @@ -2,7 +2,7 @@ from ctypes import Structure, sizeof from typing import Type from .data import Psa -from ..shared.data import Section, PsxBone +from ..shared.data import PsxBone, Section def write_section(fp, name: bytes, data_type: Type[Structure] = None, data: list = None): diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index 4224d85..09a2288 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -1,15 +1,15 @@ import typing -from typing import Dict, Generator, Set, Iterable, Optional, cast, Tuple +from typing import Dict, Generator, Set, Iterable, Optional, cast, Tuple, List import bmesh import bpy import numpy as np -from bpy.types import Collection, Context, Object, Armature +from bpy.types import Collection, Context, Object, Armature, Depsgraph from mathutils import Matrix -from .data import * -from .export.operators import get_materials_for_mesh_objects +from .data import Psk from .properties import triangle_type_and_bit_flags_to_poly_flags +from ..shared.data import Vector3 from ..shared.dfs import dfs_collection_objects, dfs_view_layer_objects, DfsObject from ..shared.helpers import convert_string_to_cp1252_bytes, \ create_psx_bones, get_coordinate_system_transform @@ -35,6 +35,19 @@ class PskBuildOptions(object): self.root_bone_name = 'ROOT' +def get_materials_for_mesh_objects(depsgraph: Depsgraph, mesh_objects: Iterable[Object]): + yielded_materials = set() + for mesh_object in mesh_objects: + evaluated_mesh_object = mesh_object.evaluated_get(depsgraph) + for i, material_slot in enumerate(evaluated_mesh_object.material_slots): + material = material_slot.material + if material is None: + raise RuntimeError('Material slot cannot be empty (index ' + str(i) + ')') + if material not in yielded_materials: + yielded_materials.add(material) + yield material + + def get_mesh_objects_for_collection(collection: Collection) -> Iterable[DfsObject]: return filter(lambda x: x.obj.type == 'MESH', dfs_collection_objects(collection)) @@ -95,8 +108,8 @@ class PskBuildResult(object): self.warnings: List[str] = [] -def _get_mesh_export_space_matrix(armature_objects: Iterable[Object], export_space: str) -> Matrix: - if not armature_objects: +def _get_mesh_export_space_matrix(armature_object: Object, export_space: str) -> Matrix: + if armature_object is None: return Matrix.Identity(4) def get_object_space_space_matrix(obj: Object) -> Matrix: @@ -104,15 +117,12 @@ def _get_mesh_export_space_matrix(armature_objects: Iterable[Object], export_spa # We neutralize the scale here because the scale is already applied to the mesh objects implicitly. return Matrix.Translation(translation) @ rotation.to_matrix().to_4x4() - match export_space: case 'WORLD': return Matrix.Identity(4) case 'ARMATURE': - return get_object_space_space_matrix(armature_objects[0]).inverted() + return get_object_space_space_matrix(armature_object).inverted() case 'ROOT': - # TODO: multiply this by the root bone's local matrix - armature_object = armature_objects[0] armature_data = cast(armature_object.data, Armature) armature_space_matrix = get_object_space_space_matrix(armature_object) @ armature_data.bones[0].matrix_local return armature_space_matrix.inverted() @@ -189,9 +199,14 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil context.window_manager.progress_begin(0, len(input_objects.mesh_dfs_objects)) coordinate_system_matrix = get_coordinate_system_transform(options.forward_axis, options.up_axis) - mesh_export_space_matrix = _get_mesh_export_space_matrix(armature_objects, options.export_space) + + # TODO: we need to store this per armature + armature_mesh_export_space_matrices: Dict[Optional[Object], Matrix] = dict() + + for armature_object in armature_objects: + armature_mesh_export_space_matrices[armature_object] = _get_mesh_export_space_matrix(armature_object, options.export_space) + scale_matrix = Matrix.Scale(options.scale, 4) - vertex_transform_matrix = scale_matrix @ coordinate_system_matrix @ mesh_export_space_matrix original_armature_object_pose_positions = {armature_object: armature_object.data.pose_position for armature_object in armature_objects} @@ -265,7 +280,8 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil case _: assert False, f'Invalid object evaluation state: {options.object_eval_state}' - vertex_offset = len(psk.points) + mesh_export_space_matrix = armature_mesh_export_space_matrices[armature_object] + vertex_transform_matrix = scale_matrix @ coordinate_system_matrix @ mesh_export_space_matrix point_transform_matrix = vertex_transform_matrix @ mesh_object.matrix_world # Vertices @@ -282,6 +298,8 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil # Wedges mesh_data.calc_loop_triangles() + vertex_offset = len(psk.points) + # Build a list of non-unique wedges. wedges = [] for loop_index, loop in enumerate(mesh_data.loops): diff --git a/io_scene_psk_psa/psk/export/operators.py b/io_scene_psk_psa/psk/export/operators.py index f737c2d..dbbd9e3 100644 --- a/io_scene_psk_psa/psk/export/operators.py +++ b/io_scene_psk_psa/psk/export/operators.py @@ -1,32 +1,19 @@ from pathlib import Path -from typing import List, Optional, cast, Iterable +from typing import Iterable, List, Optional, cast import bpy -from bpy.props import StringProperty, BoolProperty -from bpy.types import Operator, Context, Object, Collection, SpaceProperties, Depsgraph, Material +from bpy.props import BoolProperty, StringProperty +from bpy.types import Collection, Context, Depsgraph, Material, Object, Operator, SpaceProperties from bpy_extras.io_utils import ExportHelper from .properties import PskExportMixin -from ..builder import build_psk, PskBuildOptions, get_psk_input_objects_for_context, \ - get_psk_input_objects_for_collection +from ..builder import PskBuildOptions, build_psk, get_psk_input_objects_for_context, \ + get_psk_input_objects_for_collection, get_materials_for_mesh_objects from ..writer import write_psk from ...shared.helpers import populate_bone_collection_list from ...shared.ui import draw_bone_filter_mode -def get_materials_for_mesh_objects(depsgraph: Depsgraph, mesh_objects: Iterable[Object]): - yielded_materials = set() - for mesh_object in mesh_objects: - evaluated_mesh_object = mesh_object.evaluated_get(depsgraph) - for i, material_slot in enumerate(evaluated_mesh_object.material_slots): - material = material_slot.material - if material is None: - raise RuntimeError('Material slot cannot be empty (index ' + str(i) + ')') - if material not in yielded_materials: - yielded_materials.add(material) - yield material - - def populate_material_name_list(depsgraph: Depsgraph, mesh_objects: Iterable[Object], material_list): materials = list(get_materials_for_mesh_objects(depsgraph, mesh_objects)) diff --git a/io_scene_psk_psa/psk/reader.py b/io_scene_psk_psa/psk/reader.py index 7feb8fc..64c1fbd 100644 --- a/io_scene_psk_psa/psk/reader.py +++ b/io_scene_psk_psa/psk/reader.py @@ -3,8 +3,10 @@ import os import re import warnings from pathlib import Path +from typing import List -from .data import * +from ..shared.data import Section +from .data import Color, Psk, PsxBone, Vector2, Vector3 def _read_types(fp, data_class, section: Section, data): diff --git a/io_scene_psk_psa/shared/data.py b/io_scene_psk_psa/shared/data.py index eabbae3..7e4b924 100644 --- a/io_scene_psk_psa/shared/data.py +++ b/io_scene_psk_psa/shared/data.py @@ -1,4 +1,4 @@ -from ctypes import * +from ctypes import Structure, c_char, c_int32, c_float, c_ubyte from typing import Tuple from bpy.props import EnumProperty