diff --git a/io_scene_psk_psa/psa/export/operators.py b/io_scene_psk_psa/psa/export/operators.py index 8d9e3ba..926a2d9 100644 --- a/io_scene_psk_psa/psa/export/operators.py +++ b/io_scene_psk_psa/psa/export/operators.py @@ -1,9 +1,9 @@ from collections import Counter -from typing import List, Iterable, Dict, Tuple, Optional +from typing import List, Iterable, Dict, Tuple, cast as typing_cast import bpy from bpy.props import StringProperty -from bpy.types import Context, Action, Object, AnimData, TimelineMarker, Operator +from bpy.types import Context, Action, Object, AnimData, TimelineMarker, Operator, Armature from bpy_extras.io_utils import ExportHelper from .properties import ( @@ -12,6 +12,7 @@ from .properties import ( filter_sequences, get_sequences_from_name_and_frame_range, ) +from .ui import PSA_UL_export_sequences 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, PsxBoneCollection @@ -19,7 +20,7 @@ from ...shared.semver import SemanticVersion from ...shared.ui import draw_bone_filter_mode -def get_sequences_propnames_from_source(sequence_source: str) -> Optional[Tuple[str, str]]: +def get_sequences_propnames_from_source(sequence_source: str) -> Tuple[str, str]: match sequence_source: case 'ACTIONS': return 'action_list', 'action_list_index' @@ -48,7 +49,7 @@ def is_action_for_object(obj: Object, action: Action): It would simply check if it had any f-curves that corresponded to any bones in the armature. """ import re - armature_data = obj.data + armature_data = typing_cast(Armature, obj.data) bone_names = set([x.name for x in armature_data.bones]) for fcurve in action.fcurves: match = re.match(r'pose\.bones\[\"([^\"]+)\"](\[\"([^\"]+)\"])?', fcurve.data_path) @@ -57,6 +58,7 @@ def is_action_for_object(obj: Object, action: Action): bone_name = match.group(1) if bone_name in bone_names: return True + return False if version < SemanticVersion((4, 4, 0)): return is_action_for_object_legacy(action, obj) @@ -178,7 +180,7 @@ def get_animation_data_object(context: Context) -> Object: active_object = context.view_layer.objects.active - if active_object.type != 'ARMATURE': + if active_object is None or active_object.type != 'ARMATURE': raise RuntimeError('Active object must be an Armature') if pg.sequence_source != 'ACTIONS' and pg.should_override_animation_data: @@ -335,8 +337,6 @@ 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') - from .ui import PSA_UL_export_sequences - 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))) diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index e288f21..10e8b7f 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -1,9 +1,9 @@ import bmesh import bpy import numpy as np -from bpy.types import Armature, Collection, Context, Depsgraph, Object +from bpy.types import Armature, Collection, Context, Depsgraph, Object, ArmatureModifier from mathutils import Matrix -from typing import Dict, Iterable, List, Optional, Set, Tuple, cast as typing_cast +from typing import Dict, Iterable, List, Optional, Set, cast as typing_cast from .data import Psk from .properties import triangle_type_and_bit_flags_to_poly_flags from ..shared.data import Vector3 @@ -55,6 +55,8 @@ def get_mesh_objects_for_collection(collection: Collection) -> Iterable[DfsObjec def get_mesh_objects_for_context(context: Context) -> Iterable[DfsObject]: + if context.view_layer is None: + return for dfs_object in dfs_view_layer_objects(context.view_layer): if dfs_object.obj.type == 'MESH' and dfs_object.is_selected: yield dfs_object @@ -64,9 +66,10 @@ def get_armature_for_mesh_object(mesh_object: Object) -> Optional[Object]: if mesh_object.type != 'MESH': return None # Get the first armature modifier with a non-empty armature object. - for modifier in mesh_object.modifiers: - if modifier.type == 'ARMATURE' and modifier.object is not None: - return modifier.object + for modifier in filter(lambda x: x.type == 'ARMATURE', mesh_object.modifiers): + armature_modifier = typing_cast(ArmatureModifier, modifier) + if armature_modifier.object is not None: + return armature_modifier.object return None @@ -161,7 +164,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil # The material name list may contain materials that are not on the mesh objects. # Therefore, we can take the material_name_list as gospel and simply use it as a lookup table. # If a look-up fails, replace it with an empty material. - materials = [bpy.data.materials.get(x.material_name, None) for x in options.material_name_list] + materials = [bpy.data.materials.get(x, None) for x in options.material_name_list] case _: assert False, f'Invalid material order mode: {options.material_order_mode}' diff --git a/io_scene_psk_psa/psk/export/operators.py b/io_scene_psk_psa/psk/export/operators.py index faf7703..c3cb2b1 100644 --- a/io_scene_psk_psa/psk/export/operators.py +++ b/io_scene_psk_psa/psk/export/operators.py @@ -254,7 +254,7 @@ def get_psk_build_options_from_property_group(scene: Scene, pg: PskExportMixin) options.bone_collection_indices = [PsxBoneCollection(x.armature_object_name, x.armature_data_name, x.index) for x in pg.bone_collection_list if x.is_selected] options.root_bone_name = pg.root_bone_name options.material_order_mode = pg.material_order_mode - options.material_name_list = pg.material_name_list + options.material_name_list = [x.material_name for x in pg.material_name_list] match pg.transform_source: case 'SCENE':