diff --git a/io_scene_psk_psa/psk/export/operators.py b/io_scene_psk_psa/psk/export/operators.py index f0ce17c..f8460ea 100644 --- a/io_scene_psk_psa/psk/export/operators.py +++ b/io_scene_psk_psa/psk/export/operators.py @@ -264,6 +264,9 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin): self.report({'WARNING'}, f'PSK export successful with {len(result.warnings)} warnings') else: self.report({'INFO'}, f'PSK export successful') + except IOError as e: + self.report({'ERROR'}, f'Failed to write PSK file ({filepath}): {e}') + return {'CANCELLED'} except RuntimeError as e: self.report({'ERROR_INVALID_CONTEXT'}, str(e)) return {'CANCELLED'} @@ -504,6 +507,9 @@ class PSK_OT_export(Operator, ExportHelper): self.report({'WARNING'}, f'PSK export successful with {len(result.warnings)} warnings') else: self.report({'INFO'}, f'PSK export successful') + except IOError as e: + self.report({'ERROR'}, f'Failed to write PSK file ({self.filepath}): {e}') + return {'CANCELLED'} except RuntimeError as e: self.report({'ERROR_INVALID_CONTEXT'}, str(e)) return {'CANCELLED'} diff --git a/io_scene_psk_psa/shared/helpers.py b/io_scene_psk_psa/shared/helpers.py index 52249ae..bf25a44 100644 --- a/io_scene_psk_psa/shared/helpers.py +++ b/io_scene_psk_psa/shared/helpers.py @@ -350,11 +350,14 @@ def create_psx_bones( armature_tree = ObjectTree(armature_objects) - # Check that there is only one root bone. If there are multiple armature objects, the export space must be WORLD. - if len(armature_tree.root_nodes) >= 2 and export_space != 'WORLD': - root_armature_names = [node.object.name for node in armature_tree.root_nodes] - raise RuntimeError(f'When exporting multiple armatures, the Export Space must be World.\n' \ - f'The following armatures are attempting to be exported: {root_armature_names}') + if len(armature_tree.root_nodes) >= 2: + raise RuntimeError( + 'Multiple root armature objects were found. ' + 'Only one root armature object is allowed. ' + 'To use multiple armature objects, parent them to one another in a hierarchy using Bone parenting.' + ) + + # TODO: confirm this to be working with non-bone parented armature hierarchies. total_bone_count = 0 for armature_object in filter(lambda x: x.data is not None, armature_objects): @@ -398,6 +401,9 @@ def create_psx_bones( armature_object_matrix_world=armature_object.matrix_world, ) + if len(armature_psx_bones) == 0: + continue + # We have the bones in world space. If we are attaching this armature to a parent bone, we need to convert # the root bone to be relative to the target parent bone. if armature_object.parent in armature_objects: @@ -407,9 +413,13 @@ def create_psx_bones( # We just need to get the world-space location of each of the bones and get the relative pose, then # assign that location and rotation to the root bone. parent_bone_name = armature_object.parent_bone + + if parent_bone_name == '': + raise RuntimeError(f'Armature object \'{armature_object.name}\' is parented to a bone but no parent bone name is specified.') + parent_armature_data = typing_cast(Armature, armature_object.parent.data) if parent_armature_data is None: - raise RuntimeError(f'Parent object {armature_object.parent.name} is not an armature.') + raise RuntimeError(f'Parent object \'{armature_object.parent.name}\' is not an armature.') try: parent_bone = parent_armature_data.bones[parent_bone_name] except KeyError: @@ -427,12 +437,9 @@ def create_psx_bones( root_bone_rotation = BpyQuaternion((root_bone.rotation.w, root_bone.rotation.x, root_bone.rotation.y, root_bone.rotation.z)) relative_rotation = parent_bone_world_rotation.inverted() @ root_bone_rotation root_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(relative_rotation) - - case 'OBJECT': - raise NotImplementedError('Parenting armature objects to other armature objects is not yet implemented.') case _: raise RuntimeError(f'Unhandled parent type ({armature_object.parent_type}) for object {armature_object.name}.\n' - f'Parent type must be \'Object\' or \'Bone\'.' + f'Parent type must be \'Bone\'.' ) # If we are appending these bones to an existing list of bones, we need to adjust the parent indices for @@ -452,10 +459,16 @@ def create_psx_bones( for armature_object in armature_objects: if armature_object.parent not in armature_objects: continue + # This armature object is parented to another armature object that we are exporting. # First fetch the root bone indices for the two armature objects. - root_bone_index = armature_object_root_bone_indices[armature_object] - parent_root_bone_index = armature_object_root_bone_indices[armature_object.parent] + root_bone_index = armature_object_root_bone_indices.get(armature_object, None) + parent_root_bone_index = armature_object_root_bone_indices.get(armature_object.parent, None) + + if root_bone_index is None or parent_root_bone_index is None: + raise RuntimeError(f'Could not find root bone index for armature object \'{armature_object.name}\' or its parent \'{armature_object.parent.name}\'.\n' + 'This likely means that one of the armatures does not have any bones that are being exported, which is not allowed when using armature parenting between multiple armatures.' + ) match armature_object.parent_type: case 'OBJECT':