diff --git a/io_scene_psk_psa/psa/builder.py b/io_scene_psk_psa/psa/builder.py index 493fee6..774177a 100644 --- a/io_scene_psk_psa/psa/builder.py +++ b/io_scene_psk_psa/psa/builder.py @@ -103,6 +103,10 @@ def _get_pose_bone_location_and_rotation( def build_psa(context: Context, options: PsaBuildOptions) -> Psa: + + assert context.scene + assert context.window_manager + psa = Psa() armature_objects_for_bones = options.armature_objects @@ -224,6 +228,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa: export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0)))) continue + assert armature_object.pose pose_bone = armature_object.pose.bones[psx_bone.name.decode('windows-1252')] export_bones.append(PsaExportBone(pose_bone, armature_object, armature_scales[armature_object])) @@ -321,6 +326,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa: # Restore the previous actions & frame. for armature_object, action in saved_armature_object_actions.items(): + assert armature_object.animation_data armature_object.animation_data.action = action context.scene.frame_set(saved_frame_current) diff --git a/io_scene_psk_psa/psa/export/operators.py b/io_scene_psk_psa/psa/export/operators.py index 43e3088..3a50eee 100644 --- a/io_scene_psk_psa/psa/export/operators.py +++ b/io_scene_psk_psa/psa/export/operators.py @@ -461,6 +461,9 @@ class PSA_OT_export(Operator, ExportHelper): if animation_data is None: raise RuntimeError(f'No animation data for object \'{animation_data_object.name}\'') + + if context.active_object is None: + raise RuntimeError('No active object') export_sequences: List[PsaBuildSequence] = [] diff --git a/io_scene_psk_psa/psa/export/properties.py b/io_scene_psk_psa/psa/export/properties.py index eed2cc0..d5d0ce6 100644 --- a/io_scene_psk_psa/psa/export/properties.py +++ b/io_scene_psk_psa/psa/export/properties.py @@ -132,6 +132,7 @@ sampling_mode_items = ( def sequence_source_update_cb(self: 'PSA_PG_export', context: Context) -> None: armature_objects = [] + assert context.view_layer for dfs_object in dfs_view_layer_objects(context.view_layer): if dfs_object.obj.type == 'ARMATURE' and dfs_object.is_selected: armature_objects.append(dfs_object.obj) diff --git a/io_scene_psk_psa/psa/import_/operators.py b/io_scene_psk_psa/psa/import_/operators.py index d24faa4..6fca9b5 100644 --- a/io_scene_psk_psa/psa/import_/operators.py +++ b/io_scene_psk_psa/psa/import_/operators.py @@ -13,6 +13,7 @@ from ..reader import PsaReader def psa_import_poll(cls, context: Context): + assert context.view_layer and context.view_layer.objects.active active_object = context.view_layer.objects.active if active_object is None or active_object.type != 'ARMATURE': cls.poll_message_set('The active object must be an armature') @@ -32,10 +33,12 @@ class PSA_OT_import_sequences_select_from_text(Operator): return len(pg.sequence_list) > 0 def invoke(self, context, event): + assert context.window_manager return context.window_manager.invoke_props_dialog(self, width=256) def draw(self, context): layout = self.layout + assert layout pg = getattr(context.scene, 'psa_import') layout.label(icon='INFO', text='Each sequence name should be on a new line.') layout.prop(pg, 'select_text', text='') @@ -134,6 +137,8 @@ class PSA_OT_import_drag_and_drop(Operator, PsaImportMixin): warnings = [] sequences_count = 0 + assert context.view_layer and context.view_layer.objects.active + for file in self.files: psa_path = str(os.path.join(self.directory, file.name)) psa_reader = PsaReader(psa_path) @@ -157,12 +162,14 @@ class PSA_OT_import_drag_and_drop(Operator, PsaImportMixin): def invoke(self, context: Context, event): # Make sure the selected object is an obj. + assert context.view_layer and context.view_layer.objects.active active_object = context.view_layer.objects.active if active_object is None or active_object.type != 'ARMATURE': self.report({'ERROR_INVALID_CONTEXT'}, 'The active object must be an armature') return {'CANCELLED'} # Show the import operator properties in a pop-up dialog (do not use the file selector). + assert context.window_manager context.window_manager.invoke_props_dialog(self) return {'RUNNING_MODAL'} @@ -250,6 +257,8 @@ class PSA_OT_import_all(Operator, PsaImportMixin): translation_scale=self.translation_scale ) + assert context.view_layer + assert context.view_layer.objects.active result = _import_psa(context, options, self.filepath, context.view_layer.objects.active) if len(result.warnings) > 0: @@ -308,12 +317,13 @@ class PSA_OT_import(Operator, ImportHelper, PsaImportMixin): def invoke(self, context: Context, event: Event): # Attempt to load the PSA file for the pre-selected file. load_psa_file(context, self.filepath) - + assert context.window_manager context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} def draw(self, context: Context): layout = self.layout + assert layout pg = getattr(context.scene, 'psa_import') sequences_header, sequences_panel = layout.panel('sequences_panel_id', default_closed=False) @@ -434,8 +444,8 @@ class PSA_FH_import(FileHandler): # TODO: rename and add handling for PSA expor bl_file_extensions = '.psa' @classmethod - def poll_drop(cls, context: Context): - return context.area and context.area.type == 'VIEW_3D' + def poll_drop(cls, context: Context) -> bool: + return context.area is not None and context.area.type == 'VIEW_3D' _classes = ( diff --git a/io_scene_psk_psa/psa/importer.py b/io_scene_psk_psa/psa/importer.py index f56bd36..dc09d9e 100644 --- a/io_scene_psk_psa/psa/importer.py +++ b/io_scene_psk_psa/psa/importer.py @@ -145,6 +145,9 @@ def _resample_sequence_data_matrix(sequence_data_matrix: np.ndarray, frame_step: def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object, options: PsaImportOptions) -> PsaImportResult: + + assert context.window_manager + result = PsaImportResult() sequences = [psa_reader.sequences[x] for x in options.sequence_names] armature_data = typing_cast(Armature, armature_object.data) @@ -259,6 +262,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object, case 'CUSTOM': target_fps = options.fps_custom case 'SCENE': + assert context.scene target_fps = context.scene.render.fps case 'SEQUENCE': target_fps = sequence.fps diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index b8366f4..3541d0f 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -1,7 +1,7 @@ import bmesh import bpy import numpy as np -from bpy.types import Armature, Collection, Context, Depsgraph, Object, ArmatureModifier +from bpy.types import Armature, Collection, Context, Depsgraph, Object, ArmatureModifier, Mesh from mathutils import Matrix from typing import Dict, Iterable, List, Optional, Set, cast as typing_cast from .data import Psk @@ -94,9 +94,9 @@ def get_psk_input_objects_for_collection(collection: Collection) -> PskInputObje class PskBuildResult(object): - def __init__(self): - self.psk = None - self.warnings: List[str] = [] + def __init__(self, psk: Psk, warnings: list[str]): + self.psk: Psk = psk + self.warnings: List[str] = warnings def _get_mesh_export_space_matrix(armature_object: Optional[Object], export_space: str) -> Matrix: @@ -137,9 +137,12 @@ def _get_material_name_indices(obj: Object, material_names: List[str]) -> Iterab def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuildOptions) -> PskBuildResult: + + assert context.window_manager + armature_objects = list(input_objects.armature_objects) - result = PskBuildResult() + warnings: list[str] = [] psk = Psk() psx_bone_create_result = create_psx_bones( @@ -208,7 +211,8 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil # Temporarily force the armature into the rest position. # We will undo this later. for armature_object in armature_objects: - armature_object.data.pose_position = 'REST' + armature_data = typing_cast(Armature, armature_object.data) + armature_data.pose_position = 'REST' material_names = [m.name if m is not None else 'None' for m in materials] @@ -232,7 +236,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil match options.object_eval_state: case 'ORIGINAL': mesh_object = obj - mesh_data = obj.data + mesh_data = typing_cast(Mesh, obj.data) case 'EVALUATED': # Create a copy of the mesh object after non-armature modifiers are applied. depsgraph = context.evaluated_depsgraph_get() @@ -299,7 +303,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil mesh_data.calc_loop_triangles() if mesh_data.uv_layers.active is None: - result.warnings.append(f'"{mesh_object.name}" has no active UV Map') + warnings.append(f'"{mesh_object.name}" has no active UV Map') # Build a list of non-unique wedges. wedges = [] @@ -423,13 +427,12 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil # Restore the original pose position of the armature objects. for armature_object, pose_position in original_armature_object_pose_positions.items(): - armature_object.data.pose_position = pose_position + armature_data = typing_cast(Armature, armature_object.data) + armature_data.pose_position = pose_position # https://github.com/DarklightGames/io_scene_psk_psa/issues/129. psk.sort_and_normalize_weights() context.window_manager.progress_end() - result.psk = psk - - return result + return PskBuildResult(psk, warnings) diff --git a/io_scene_psk_psa/psk/export/operators.py b/io_scene_psk_psa/psk/export/operators.py index 529022b..e9f7f5c 100644 --- a/io_scene_psk_psa/psk/export/operators.py +++ b/io_scene_psk_psa/psk/export/operators.py @@ -3,7 +3,7 @@ from typing import Iterable, List, Optional, cast as typing_cast import bpy from bpy.props import BoolProperty, StringProperty -from bpy.types import Collection, Context, Depsgraph, Material, Object, Operator, SpaceProperties, Scene +from bpy.types import Context, Depsgraph, Material, Object, Operator, Scene from bpy_extras.io_utils import ExportHelper from .properties import PskExportMixin @@ -91,6 +91,7 @@ class PSK_OT_populate_material_name_list(Operator): self.report({'ERROR_INVALID_CONTEXT'}, 'No valid export operator found in context') return {'CANCELLED'} depsgraph = context.evaluated_depsgraph_get() + assert context.collection input_objects = get_psk_input_objects_for_collection(context.collection) try: populate_material_name_list(depsgraph, [x.obj for x in input_objects.mesh_dfs_objects], export_operator.material_name_list) @@ -115,6 +116,7 @@ class PSK_OT_material_list_name_add(Operator): name: StringProperty(search=material_list_names_search_cb, name='Material Name', default='None') def invoke(self, context, event): + assert context.window_manager return context.window_manager.invoke_props_dialog(self) def execute(self, context): @@ -266,7 +268,10 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin): collection: StringProperty(options={'HIDDEN'}) def execute(self, context): - collection = bpy.data.collections.get(self.collection) + collection = bpy.data.collections.get(self.collection, None) + + if collection is not None: + return {'CANCELLED'} try: input_objects = get_psk_input_objects_for_collection(collection) @@ -295,6 +300,8 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin): def draw(self, context: Context): layout = self.layout + assert layout is not None + flow = layout.grid_flow(row_major=True) flow.use_property_split = True flow.use_property_decorate = False @@ -376,6 +383,8 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin): flow.enabled = False case 'CUSTOM': transform_source = self + case _: + assert False, f'Invalid transform source: {self.transform_source}' flow.prop(transform_source, 'scale') flow.prop(transform_source, 'forward_axis') @@ -414,6 +423,7 @@ class PSK_OT_export(Operator, ExportHelper): self.report({'ERROR_INVALID_CONTEXT'}, str(e)) return {'CANCELLED'} + assert context.window_manager context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} @@ -421,6 +431,8 @@ class PSK_OT_export(Operator, ExportHelper): def draw(self, context): layout = self.layout + assert layout + pg = getattr(context.scene, 'psk_export') # Mesh @@ -490,6 +502,8 @@ class PSK_OT_export(Operator, ExportHelper): def execute(self, context): pg = getattr(context.scene, 'psk_export') + assert context.scene + input_objects = get_psk_input_objects_for_context(context) options = get_psk_build_options_from_property_group(context.scene, pg) diff --git a/io_scene_psk_psa/psk/export/properties.py b/io_scene_psk_psa/psk/export/properties.py index ab32bd7..8837474 100644 --- a/io_scene_psk_psa/psk/export/properties.py +++ b/io_scene_psk_psa/psk/export/properties.py @@ -52,7 +52,7 @@ class PskExportMixin(ExportSpaceMixin, TransformMixin, PsxBoneExportMixin): material_name_list: CollectionProperty(type=PSK_PG_material_name_list_item) material_name_list_index: IntProperty(default=0) should_export_vertex_normals: BoolProperty( - 'Export Vertex Normals', + name='Export Vertex Normals', default=False, description='Export VTXNORMS section.' ) diff --git a/io_scene_psk_psa/psk/import_/operators.py b/io_scene_psk_psa/psk/import_/operators.py index 2ffa1ab..b2d0cb3 100644 --- a/io_scene_psk_psa/psk/import_/operators.py +++ b/io_scene_psk_psa/psk/import_/operators.py @@ -9,6 +9,7 @@ from ..importer import PskImportOptions, import_psk from ..properties import PskImportMixin from ..reader import read_psk + def get_psk_import_options_from_properties(property_group: PskImportMixin): options = PskImportOptions() options.should_import_mesh = property_group.should_import_mesh @@ -109,6 +110,7 @@ class PSK_OT_import(Operator, ImportHelper, PskImportMixin): return {'FINISHED'} def draw(self, context): + assert self.layout psk_import_draw(self.layout, self) @@ -122,13 +124,15 @@ class PSK_OT_import_drag_and_drop(Operator, PskImportMixin): files: CollectionProperty(type=OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'}) @classmethod - def poll(cls, context): - return context.area and context.area.type == 'VIEW_3D' + def poll(cls, context) -> bool: + return context.area is not None and context.area.type == 'VIEW_3D' def draw(self, context): + assert self.layout psk_import_draw(self.layout, self) def invoke(self, context, event): + assert context.window_manager context.window_manager.invoke_props_dialog(self) return {'RUNNING_MODAL'} @@ -167,8 +171,8 @@ class PSK_FH_import(FileHandler): bl_file_extensions = '.psk;.pskx' @classmethod - def poll_drop(cls, context: Context): - return context.area and context.area.type == 'VIEW_3D' + def poll_drop(cls, context: Context) -> bool: + return context.area is not None and context.area.type == 'VIEW_3D' _classes = ( diff --git a/io_scene_psk_psa/psk/importer.py b/io_scene_psk_psa/psk/importer.py index d3b3fb8..f72ad22 100644 --- a/io_scene_psk_psa/psk/importer.py +++ b/io_scene_psk_psa/psk/importer.py @@ -62,6 +62,9 @@ def import_psk(psk: Psk, context: Context, name: str, options: PskImportOptions) armature_object = None mesh_object = None + assert context.scene + assert bpy.context.view_layer + if options.should_import_armature: # Armature armature_data = bpy.data.armatures.new(name) diff --git a/io_scene_psk_psa/psk/ui.py b/io_scene_psk_psa/psk/ui.py index 58499f4..e98e792 100644 --- a/io_scene_psk_psa/psk/ui.py +++ b/io_scene_psk_psa/psk/ui.py @@ -15,6 +15,7 @@ class PSK_PT_material(Panel): def draw(self, context): layout = self.layout + assert layout is not None layout.use_property_split = True layout.use_property_decorate = False material = context.material