From 42a859e24b6b5a6111feaf9701c88d8ac24cfd9a Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Tue, 26 Nov 2024 04:52:28 -0800 Subject: [PATCH] Added scale factor to PSK export Also a load of cleanup I don't have time to catalog --- io_scene_psk_psa/psa/export/operators.py | 152 +++++++++++----------- io_scene_psk_psa/psa/export/properties.py | 34 +++-- io_scene_psk_psa/psa/export/ui.py | 5 +- io_scene_psk_psa/psk/builder.py | 15 ++- io_scene_psk_psa/psk/export/operators.py | 18 ++- io_scene_psk_psa/psk/export/properties.py | 9 +- io_scene_psk_psa/psk/reader.py | 72 +++++----- io_scene_psk_psa/shared/helpers.py | 27 ++-- 8 files changed, 188 insertions(+), 144 deletions(-) diff --git a/io_scene_psk_psa/psa/export/operators.py b/io_scene_psk_psa/psa/export/operators.py index 8658068..8c2acfe 100644 --- a/io_scene_psk_psa/psa/export/operators.py +++ b/io_scene_psk_psa/psa/export/operators.py @@ -8,7 +8,8 @@ from bpy.types import Context, Armature, Action, Object, AnimData, TimelineMarke from bpy_extras.io_utils import ExportHelper from bpy_types import Operator -from .properties import PSA_PG_export, PSA_PG_export_action_list_item, filter_sequences +from .properties import PSA_PG_export, PSA_PG_export_action_list_item, filter_sequences, \ + get_sequences_from_name_and_frame_range from ..builder import build_psa, PsaBuildSequence, PsaBuildOptions from ..writer import write_psa from ...shared.helpers import populate_bone_collection_list, get_nla_strips_in_frame_range @@ -144,7 +145,7 @@ def get_timeline_marker_sequence_frame_ranges(animation_data: AnimData, context: if next_marker_index < len(sorted_timeline_markers): # There is a next marker. Use that next marker's frame position as the last frame of this sequence. frame_end = sorted_timeline_markers[next_marker_index].frame - nla_strips = get_nla_strips_in_frame_range(animation_data, marker.frame, frame_end) + nla_strips = list(get_nla_strips_in_frame_range(animation_data, marker.frame, frame_end)) if len(nla_strips) > 0: frame_end = min(frame_end, max(map(lambda nla_strip: nla_strip.frame_end, nla_strips))) frame_start = max(frame_start, min(map(lambda nla_strip: nla_strip.frame_start, nla_strips))) @@ -168,20 +169,6 @@ def get_timeline_marker_sequence_frame_ranges(animation_data: AnimData, context: return sequence_frame_ranges -def get_sequences_from_name_and_frame_range(name: str, frame_start: int, frame_end: int) -> List[Tuple[str, int, int]]: - reversed_pattern = r'(.+)/(.+)' - reversed_match = re.match(reversed_pattern, name) - if reversed_match: - forward_name = reversed_match.group(1) - backwards_name = reversed_match.group(2) - return [ - (forward_name, frame_start, frame_end), - (backwards_name, frame_end, frame_start) - ] - else: - return [(name, frame_start, frame_end)] - - def get_sequences_from_action(action: Action) -> List[Tuple[str, int, int]]: frame_start = int(action.frame_range[0]) frame_end = int(action.frame_range[1]) @@ -266,16 +253,22 @@ class PSA_OT_export(Operator, ExportHelper): row.operator(PSA_OT_export_actions_select_all.bl_idname, text='All', icon='CHECKBOX_HLT') row.operator(PSA_OT_export_actions_deselect_all.bl_idname, text='None', icon='CHECKBOX_DEHLT') - # ACTIONS - if pg.sequence_source == 'ACTIONS': - rows = max(3, min(len(pg.action_list), 10)) - sequences_panel.template_list('PSA_UL_export_sequences', '', pg, 'action_list', pg, 'action_list_index', rows=rows) - elif pg.sequence_source == 'TIMELINE_MARKERS': - rows = max(3, min(len(pg.marker_list), 10)) - sequences_panel.template_list('PSA_UL_export_sequences', '', pg, 'marker_list', pg, 'marker_list_index', rows=rows) - elif pg.sequence_source == 'NLA_TRACK_STRIPS': - rows = max(3, min(len(pg.nla_strip_list), 10)) - sequences_panel.template_list('PSA_UL_export_sequences', '', pg, 'nla_strip_list', pg, 'nla_strip_list_index', rows=rows) + from .ui import PSA_UL_export_sequences + + def get_sequences_propnames_from_source(sequence_source: str) -> Tuple[str, str]: + match sequence_source: + case 'ACTIONS': + return 'action_list', 'action_list_index' + case 'TIMELINE_MARKERS': + return 'marker_list', 'marker_list_index' + case 'NLA_TRACK_STRIPS': + return 'nla_strip_list', 'nla_strip_list_index' + case _: + raise ValueError(f'Unhandled sequence source: {sequence_source}') + + propname, active_propname = get_sequences_propnames_from_source(pg.sequence_source) + sequences_panel.template_list(PSA_UL_export_sequences.bl_idname, '', pg, propname, pg, active_propname, + rows=max(3, min(len(getattr(pg, propname), 10)))) flow = sequences_panel.grid_flow() flow.use_property_split = True @@ -379,43 +372,44 @@ class PSA_OT_export(Operator, ExportHelper): export_sequences: List[PsaBuildSequence] = [] - if pg.sequence_source == 'ACTIONS': - for action_item in filter(lambda x: x.is_selected, pg.action_list): - if len(action_item.action.fcurves) == 0: - continue - export_sequence = PsaBuildSequence() - export_sequence.nla_state.action = action_item.action - export_sequence.name = action_item.name - export_sequence.nla_state.frame_start = action_item.frame_start - export_sequence.nla_state.frame_end = action_item.frame_end - export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [action_item.action]) - export_sequence.compression_ratio = action_item.action.psa_export.compression_ratio - export_sequence.key_quota = action_item.action.psa_export.key_quota - export_sequences.append(export_sequence) - elif pg.sequence_source == 'TIMELINE_MARKERS': - for marker_item in filter(lambda x: x.is_selected, pg.marker_list): - export_sequence = PsaBuildSequence() - export_sequence.name = marker_item.name - export_sequence.nla_state.action = None - export_sequence.nla_state.frame_start = marker_item.frame_start - export_sequence.nla_state.frame_end = marker_item.frame_end - nla_strips_actions = set( - map(lambda x: x.action, get_nla_strips_in_frame_range(animation_data, marker_item.frame_start, marker_item.frame_end))) - export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, nla_strips_actions) - export_sequences.append(export_sequence) - elif pg.sequence_source == 'NLA_TRACK_STRIPS': - for nla_strip_item in filter(lambda x: x.is_selected, pg.nla_strip_list): - export_sequence = PsaBuildSequence() - export_sequence.name = nla_strip_item.name - export_sequence.nla_state.action = None - export_sequence.nla_state.frame_start = nla_strip_item.frame_start - export_sequence.nla_state.frame_end = nla_strip_item.frame_end - export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [nla_strip_item.action]) - export_sequence.compression_ratio = nla_strip_item.action.psa_export.compression_ratio - export_sequence.key_quota = nla_strip_item.action.psa_export.key_quota - export_sequences.append(export_sequence) - else: - raise ValueError(f'Unhandled sequence source: {pg.sequence_source}') + match pg.sequence_source: + case 'ACTIONS': + for action_item in filter(lambda x: x.is_selected, pg.action_list): + if len(action_item.action.fcurves) == 0: + continue + export_sequence = PsaBuildSequence() + export_sequence.nla_state.action = action_item.action + export_sequence.name = action_item.name + export_sequence.nla_state.frame_start = action_item.frame_start + export_sequence.nla_state.frame_end = action_item.frame_end + export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [action_item.action]) + export_sequence.compression_ratio = action_item.action.psa_export.compression_ratio + export_sequence.key_quota = action_item.action.psa_export.key_quota + export_sequences.append(export_sequence) + case 'TIMELINE_MARKERS': + for marker_item in filter(lambda x: x.is_selected, pg.marker_list): + export_sequence = PsaBuildSequence() + export_sequence.name = marker_item.name + export_sequence.nla_state.action = None + export_sequence.nla_state.frame_start = marker_item.frame_start + export_sequence.nla_state.frame_end = marker_item.frame_end + nla_strips_actions = set( + map(lambda x: x.action, get_nla_strips_in_frame_range(animation_data, marker_item.frame_start, marker_item.frame_end))) + export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, nla_strips_actions) + export_sequences.append(export_sequence) + case 'NLA_TRACK_STRIPS': + for nla_strip_item in filter(lambda x: x.is_selected, pg.nla_strip_list): + export_sequence = PsaBuildSequence() + export_sequence.name = nla_strip_item.name + export_sequence.nla_state.action = None + export_sequence.nla_state.frame_start = nla_strip_item.frame_start + export_sequence.nla_state.frame_end = nla_strip_item.frame_end + export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [nla_strip_item.action]) + export_sequence.compression_ratio = nla_strip_item.action.psa_export.compression_ratio + export_sequence.key_quota = nla_strip_item.action.psa_export.key_quota + export_sequences.append(export_sequence) + case _: + raise ValueError(f'Unhandled sequence source: {pg.sequence_source}') options = PsaBuildOptions() options.animation_data = animation_data @@ -448,13 +442,15 @@ class PSA_OT_export_actions_select_all(Operator): @classmethod def get_item_list(cls, context): pg = context.scene.psa_export - if pg.sequence_source == 'ACTIONS': - return pg.action_list - elif pg.sequence_source == 'TIMELINE_MARKERS': - return pg.marker_list - elif pg.sequence_source == 'NLA_TRACK_STRIPS': - return pg.nla_strip_list - return None + match pg.sequence_source: + case 'ACTIONS': + return pg.action_list + case 'TIMELINE_MARKERS': + return pg.marker_list + case 'NLA_TRACK_STRIPS': + return pg.nla_strip_list + case _: + return None @classmethod def poll(cls, context): @@ -481,13 +477,15 @@ class PSA_OT_export_actions_deselect_all(Operator): @classmethod def get_item_list(cls, context): pg = context.scene.psa_export - if pg.sequence_source == 'ACTIONS': - return pg.action_list - elif pg.sequence_source == 'TIMELINE_MARKERS': - return pg.marker_list - elif pg.sequence_source == 'NLA_TRACK_STRIPS': - return pg.nla_strip_list - return None + match pg.sequence_source: + case 'ACTIONS': + return pg.action_list + case 'TIMELINE_MARKERS': + return pg.marker_list + case 'NLA_TRACK_STRIPS': + return pg.nla_strip_list + case _: + return None @classmethod def poll(cls, context): diff --git a/io_scene_psk_psa/psa/export/properties.py b/io_scene_psk_psa/psa/export/properties.py index 3efd072..35665e8 100644 --- a/io_scene_psk_psa/psa/export/properties.py +++ b/io_scene_psk_psa/psa/export/properties.py @@ -1,7 +1,7 @@ import re import sys from fnmatch import fnmatch -from typing import List, Optional +from typing import List, Optional, Tuple from bpy.props import BoolProperty, PointerProperty, EnumProperty, FloatProperty, CollectionProperty, IntProperty, \ StringProperty @@ -42,6 +42,20 @@ class PSA_PG_export_nla_strip_list_item(PropertyGroup): is_selected: BoolProperty(default=True) +def get_sequences_from_name_and_frame_range(name: str, frame_start: int, frame_end: int) -> List[Tuple[str, int, int]]: + reversed_pattern = r'(.+)/(.+)' + reversed_match = re.match(reversed_pattern, name) + if reversed_match: + forward_name = reversed_match.group(1) + backwards_name = reversed_match.group(2) + return [ + (forward_name, frame_start, frame_end), + (backwards_name, frame_end, frame_start) + ] + else: + return [(name, frame_start, frame_end)] + + def nla_track_update_cb(self: 'PSA_PG_export', context: Context) -> None: self.nla_strip_list.clear() match = re.match(r'^(\d+).+$', self.nla_track) @@ -52,11 +66,12 @@ def nla_track_update_cb(self: 'PSA_PG_export', context: Context) -> None: return nla_track = animation_data.nla_tracks[self.nla_track_index] for nla_strip in nla_track.strips: - strip: PSA_PG_export_nla_strip_list_item = self.nla_strip_list.add() - strip.action = nla_strip.action - strip.name = nla_strip.name - strip.frame_start = nla_strip.frame_start - strip.frame_end = nla_strip.frame_end + for sequence_name, frame_start, frame_end in get_sequences_from_name_and_frame_range(nla_strip.name, nla_strip.frame_start, nla_strip.frame_end): + strip: PSA_PG_export_nla_strip_list_item = self.nla_strip_list.add() + strip.action = nla_strip.action + strip.name = sequence_name + strip.frame_start = frame_start + strip.frame_end = frame_end def get_animation_data(pg: 'PSA_PG_export', context: Context) -> Optional[AnimData]: @@ -69,10 +84,9 @@ def get_animation_data(pg: 'PSA_PG_export', context: Context) -> Optional[AnimDa def nla_track_search_cb(self, context: Context, edit_text: str): pg = getattr(context.scene, 'psa_export') animation_data = get_animation_data(pg, context) - if animation_data is None: - return - for index, nla_track in enumerate(animation_data.nla_tracks): - yield f'{index} - {nla_track.name}' + if animation_data is not None: + for index, nla_track in enumerate(animation_data.nla_tracks): + yield f'{index} - {nla_track.name}' def animation_data_override_update_cb(self: 'PSA_PG_export', context: Context): diff --git a/io_scene_psk_psa/psa/export/ui.py b/io_scene_psk_psa/psa/export/ui.py index 1c92337..82d6acb 100644 --- a/io_scene_psk_psa/psa/export/ui.py +++ b/io_scene_psk_psa/psa/export/ui.py @@ -1,4 +1,4 @@ -from typing import cast +import typing from bpy.types import UIList @@ -6,6 +6,7 @@ from .properties import PSA_PG_export_action_list_item, filter_sequences class PSA_UL_export_sequences(UIList): + bl_idname = 'PSA_UL_export_sequences' def __init__(self): super(PSA_UL_export_sequences, self).__init__() @@ -13,7 +14,7 @@ class PSA_UL_export_sequences(UIList): self.use_filter_show = True def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - item = cast(PSA_PG_export_action_list_item, item) + item = typing.cast(PSA_PG_export_action_list_item, item) is_pose_marker = hasattr(item, 'is_pose_marker') and item.is_pose_marker layout.prop(item, 'is_selected', icon_only=True, text=item.name) if hasattr(item, 'action') and item.action is not None and item.action.asset_data is not None: diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index f171413..89cd041 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -1,9 +1,10 @@ +import typing from typing import Optional import bmesh -import bpy import numpy as np -from bpy.types import Armature, Material, Collection, Context +from bpy.types import Material, Collection, Context +from mathutils import Matrix from .data import * from .properties import triangle_type_and_bit_flags_to_poly_flags @@ -23,6 +24,7 @@ class PskBuildOptions(object): self.object_eval_state = 'EVALUATED' self.materials: List[Material] = [] self.should_enforce_bone_name_restrictions = False + self.scale = 1.0 def get_mesh_objects_for_collection(collection: Collection, should_exclude_hidden_meshes: bool = True): @@ -40,7 +42,7 @@ def get_mesh_objects_for_context(context: Context): yield obj -def get_armature_for_mesh_objects(mesh_objects: List[Object]) -> Optional[Object]: +def get_armature_for_mesh_objects(mesh_objects: Iterable[Object]) -> Optional[Object]: # Ensure that there are either no armature modifiers (static mesh) or that there is exactly one armature modifier # object shared between all meshes. armature_modifier_objects = set() @@ -100,6 +102,8 @@ def build_psk(context, input_objects: PskInputObjects, options: PskBuildOptions) psk = Psk() bones = [] + scale_matrix = Matrix.Scale(options.scale, 4) + if armature_object is None or len(armature_object.data.bones) == 0: # If the mesh has no armature object or no bones, simply assign it a dummy bone at the root to satisfy the # requirement that a PSK file must have at least one bone. @@ -151,6 +155,8 @@ def build_psk(context, input_objects: PskInputObjects, options: PskBuildOptions) rotation = bone_rotation @ local_rotation rotation.conjugate() + location = scale_matrix @ location + psk_bone.location.x = location.x psk_bone.location.y = location.y psk_bone.location.z = location.z @@ -230,11 +236,12 @@ def build_psk(context, input_objects: PskInputObjects, options: PskBuildOptions) armature_object.data.pose_position = old_pose_position vertex_offset = len(psk.points) + matrix_world = scale_matrix @ mesh_object.matrix_world # VERTICES for vertex in mesh_data.vertices: point = Vector3() - v = mesh_object.matrix_world @ vertex.co + v = matrix_world @ vertex.co point.x = v.x point.y = v.y point.z = v.z diff --git a/io_scene_psk_psa/psk/export/operators.py b/io_scene_psk_psa/psk/export/operators.py index a08dcee..91364a5 100644 --- a/io_scene_psk_psa/psk/export/operators.py +++ b/io_scene_psk_psa/psk/export/operators.py @@ -1,7 +1,7 @@ from typing import List import bpy -from bpy.props import StringProperty, BoolProperty, EnumProperty +from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty from bpy.types import Operator, Context, Object from bpy_extras.io_utils import ExportHelper @@ -32,6 +32,7 @@ def get_materials_for_mesh_objects(mesh_objects: List[Object]): materials.append(material) return materials + def populate_material_list(mesh_objects, material_list): materials = get_materials_for_mesh_objects(mesh_objects) material_list.clear() @@ -107,6 +108,13 @@ class PSK_OT_export_collection(Operator, ExportHelper): name='Visible Only', description='Export only visible meshes' ) + scale: FloatProperty( + name='Scale', + default=1.0, + description='Scale factor to apply to the exported mesh and armature', + min=0.0001, + soft_max=100.0 + ) def execute(self, context): collection = bpy.data.collections.get(self.collection) @@ -122,6 +130,7 @@ class PSK_OT_export_collection(Operator, ExportHelper): options.object_eval_state = self.object_eval_state options.materials = get_materials_for_mesh_objects(input_objects.mesh_objects) options.should_enforce_bone_name_restrictions = self.should_enforce_bone_name_restrictions + options.scale = self.scale try: result = build_psk(context, input_objects, options) @@ -141,6 +150,12 @@ class PSK_OT_export_collection(Operator, ExportHelper): def draw(self, context: Context): layout = self.layout + flow = layout.grid_flow(row_major=True) + flow.use_property_split = True + flow.use_property_decorate = False + + flow.prop(self, 'scale') + # MESH mesh_header, mesh_panel = layout.panel('Mesh', default_closed=False) mesh_header.label(text='Mesh', icon='MESH_DATA') @@ -260,6 +275,7 @@ class PSK_OT_export(Operator, ExportHelper): options.object_eval_state = pg.object_eval_state options.materials = [m.material for m in pg.material_list] options.should_enforce_bone_name_restrictions = pg.should_enforce_bone_name_restrictions + options.scale = pg.scale try: result = build_psk(context, input_objects, options) diff --git a/io_scene_psk_psa/psk/export/properties.py b/io_scene_psk_psa/psk/export/properties.py index 6dd31da..551a27c 100644 --- a/io_scene_psk_psa/psk/export/properties.py +++ b/io_scene_psk_psa/psk/export/properties.py @@ -1,4 +1,4 @@ -from bpy.props import EnumProperty, CollectionProperty, IntProperty, BoolProperty, PointerProperty +from bpy.props import EnumProperty, CollectionProperty, IntProperty, BoolProperty, PointerProperty, FloatProperty from bpy.types import PropertyGroup, Material from ...shared.types import PSX_PG_bone_collection_list_item @@ -42,6 +42,13 @@ class PSK_PG_export(PropertyGroup): description='Enforce that bone names must only contain letters, numbers, spaces, hyphens and underscores.\n\n' 'Depending on the engine, improper bone names might not be referenced correctly by scripts' ) + scale: FloatProperty( + name='Scale', + default=1.0, + description='Scale factor to apply to the exported mesh', + min=0.0001, + soft_max=100.0 + ) classes = ( diff --git a/io_scene_psk_psa/psk/reader.py b/io_scene_psk_psa/psk/reader.py index f71a006..49864a6 100644 --- a/io_scene_psk_psa/psk/reader.py +++ b/io_scene_psk_psa/psk/reader.py @@ -36,41 +36,43 @@ def read_psk(path: str) -> Psk: while fp.read(1): fp.seek(-1, 1) section = Section.from_buffer_copy(fp.read(ctypes.sizeof(Section))) - if section.name == b'ACTRHEAD': - pass - elif section.name == b'PNTS0000': - _read_types(fp, Vector3, section, psk.points) - elif section.name == b'VTXW0000': - if section.data_size == ctypes.sizeof(Psk.Wedge16): - _read_types(fp, Psk.Wedge16, section, psk.wedges) - elif section.data_size == ctypes.sizeof(Psk.Wedge32): - _read_types(fp, Psk.Wedge32, section, psk.wedges) - else: - raise RuntimeError('Unrecognized wedge format') - elif section.name == b'FACE0000': - _read_types(fp, Psk.Face, section, psk.faces) - elif section.name == b'MATT0000': - _read_types(fp, Psk.Material, section, psk.materials) - elif section.name == b'REFSKELT': - _read_types(fp, Psk.Bone, section, psk.bones) - elif section.name == b'RAWWEIGHTS': - _read_types(fp, Psk.Weight, section, psk.weights) - elif section.name == b'FACE3200': - _read_types(fp, Psk.Face32, section, psk.faces) - elif section.name == b'VERTEXCOLOR': - _read_types(fp, Color, section, psk.vertex_colors) - elif section.name.startswith(b'EXTRAUVS'): - _read_types(fp, Vector2, section, psk.extra_uvs) - elif section.name == b'VTXNORMS': - _read_types(fp, Vector3, section, psk.vertex_normals) - elif section.name == b'MRPHINFO': - _read_types(fp, Psk.MorphInfo, section, psk.morph_infos) - elif section.name == b'MRPHDATA': - _read_types(fp, Psk.MorphData, section, psk.morph_data) - else: - # Section is not handled, skip it. - fp.seek(section.data_size * section.data_count, os.SEEK_CUR) - warnings.warn(f'Unrecognized section "{section.name} at position {fp.tell():15}"') + match section.name: + case b'ACTRHEAD': + pass + case b'PNTS0000': + _read_types(fp, Vector3, section, psk.points) + case b'VTXW0000': + if section.data_size == ctypes.sizeof(Psk.Wedge16): + _read_types(fp, Psk.Wedge16, section, psk.wedges) + elif section.data_size == ctypes.sizeof(Psk.Wedge32): + _read_types(fp, Psk.Wedge32, section, psk.wedges) + else: + raise RuntimeError('Unrecognized wedge format') + case b'FACE0000': + _read_types(fp, Psk.Face, section, psk.faces) + case b'MATT0000': + _read_types(fp, Psk.Material, section, psk.materials) + case b'REFSKELT': + _read_types(fp, Psk.Bone, section, psk.bones) + case b'RAWWEIGHTS': + _read_types(fp, Psk.Weight, section, psk.weights) + case b'FACE3200': + _read_types(fp, Psk.Face32, section, psk.faces) + case b'VERTEXCOLOR': + _read_types(fp, Color, section, psk.vertex_colors) + case b'VTXNORMS': + _read_types(fp, Vector3, section, psk.vertex_normals) + case b'MRPHINFO': + _read_types(fp, Psk.MorphInfo, section, psk.morph_infos) + case b'MRPHDATA': + _read_types(fp, Psk.MorphData, section, psk.morph_data) + case _: + if section.name.startswith(b'EXTRAUVS'): + _read_types(fp, Vector2, section, psk.extra_uvs) + else: + # Section is not handled, skip it. + fp.seek(section.data_size * section.data_count, os.SEEK_CUR) + warnings.warn(f'Unrecognized section "{section.name} at position {fp.tell():15}"') ''' UEViewer exports a sidecar file (*.props.txt) with fully-qualified reference paths for each material diff --git a/io_scene_psk_psa/shared/helpers.py b/io_scene_psk_psa/shared/helpers.py index a352fa9..79c5b9a 100644 --- a/io_scene_psk_psa/shared/helpers.py +++ b/io_scene_psk_psa/shared/helpers.py @@ -1,9 +1,10 @@ import re -import typing -from typing import List, Iterable +from typing import List, Iterable, cast -import bpy.types -from bpy.types import NlaStrip, Object, AnimData +import bpy +from bpy.props import CollectionProperty +from bpy.types import AnimData, Object +from bpy.types import Armature def rgb_to_srgb(c: float): @@ -13,10 +14,9 @@ def rgb_to_srgb(c: float): return 12.92 * c -def get_nla_strips_in_frame_range(animation_data: AnimData, frame_min: float, frame_max: float) -> List[NlaStrip]: +def get_nla_strips_in_frame_range(animation_data: AnimData, frame_min: float, frame_max: float): if animation_data is None: - return [] - strips = [] + return for nla_track in animation_data.nla_tracks: if nla_track.mute: continue @@ -24,11 +24,10 @@ def get_nla_strips_in_frame_range(animation_data: AnimData, frame_min: float, fr if (strip.frame_start < frame_min and strip.frame_end > frame_max) or \ (frame_min <= strip.frame_start < frame_max) or \ (frame_min < strip.frame_end <= frame_max): - strips.append(strip) - return strips + yield strip -def populate_bone_collection_list(armature_object: Object, bone_collection_list: bpy.props.CollectionProperty) -> None: +def populate_bone_collection_list(armature_object: Object, bone_collection_list: CollectionProperty) -> None: """ Updates the bone collections collection. @@ -53,7 +52,7 @@ def populate_bone_collection_list(armature_object: Object, bone_collection_list: bone_collection_list.clear() - armature = armature_object.data + armature = cast(Armature, armature_object.data) if armature is None: return @@ -82,7 +81,7 @@ def check_bone_names(bone_names: Iterable[str]): f'You can bypass this by disabling "Enforce Bone Name Restrictions" in the export settings.') -def get_export_bone_names(armature_object: Object, bone_filter_mode: str, bone_collection_indices: List[int]) -> List[str]: +def get_export_bone_names(armature_object: Object, bone_filter_mode: str, bone_collection_indices: Iterable[int]) -> List[str]: """ Returns a sorted list of bone indices that should be exported for the given bone filter mode and bone collections. @@ -90,13 +89,13 @@ def get_export_bone_names(armature_object: Object, bone_filter_mode: str, bone_c :param armature_object: Blender object with type 'ARMATURE' :param bone_filter_mode: One of ['ALL', 'BONE_COLLECTIONS'] - :param bone_collection_indices: List of bone collection indices to be exported. + :param bone_collection_indices: A list of bone collection indices to export. :return: A sorted list of bone indices that should be exported. """ if armature_object is None or armature_object.type != 'ARMATURE': raise ValueError('An armature object must be supplied') - armature_data = typing.cast(bpy.types.Armature, armature_object.data) + armature_data = cast(Armature, armature_object.data) bones = armature_data.bones bone_names = [x.name for x in bones]