From ce1a4112005324caf333755b2a23bacc120f1731 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Sat, 17 Feb 2024 23:01:42 -0800 Subject: [PATCH 1/6] Fix for issue #76 --- io_scene_psk_psa/psa/export/operators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_psk_psa/psa/export/operators.py b/io_scene_psk_psa/psa/export/operators.py index a44f02c..aa5d88d 100644 --- a/io_scene_psk_psa/psa/export/operators.py +++ b/io_scene_psk_psa/psa/export/operators.py @@ -110,7 +110,7 @@ def get_animation_data_object(context: Context) -> Object: if active_object.type != 'ARMATURE': raise RuntimeError('Selected object must be an Armature') - if pg.should_override_animation_data: + if pg.sequence_source != 'ACTIONS' and pg.should_override_animation_data: animation_data_object = pg.animation_data_override else: animation_data_object = active_object From ead1e3c793e317d5be1509805cf5be4a1fe2260a Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Wed, 28 Feb 2024 00:51:33 -0800 Subject: [PATCH 2/6] Initial commit of UT99 poly flags --- io_scene_psk_psa/__init__.py | 8 +++++ io_scene_psk_psa/psk/builder.py | 17 +++++----- io_scene_psk_psa/psk/export/operators.py | 14 ++++----- io_scene_psk_psa/psk/export/properties.py | 6 ++-- io_scene_psk_psa/psk/export/ui.py | 2 +- io_scene_psk_psa/psk/properties.py | 38 +++++++++++++++++++++++ io_scene_psk_psa/psk/ui.py | 28 +++++++++++++++++ io_scene_psk_psa/types.py | 2 +- 8 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 io_scene_psk_psa/psk/properties.py create mode 100644 io_scene_psk_psa/psk/ui.py diff --git a/io_scene_psk_psa/__init__.py b/io_scene_psk_psa/__init__.py index 7570fe9..4076dd9 100644 --- a/io_scene_psk_psa/__init__.py +++ b/io_scene_psk_psa/__init__.py @@ -24,6 +24,8 @@ if 'bpy' in locals(): importlib.reload(psk_writer) importlib.reload(psk_builder) importlib.reload(psk_importer) + importlib.reload(psk_properties) + importlib.reload(psk_ui) importlib.reload(psk_export_properties) importlib.reload(psk_export_operators) importlib.reload(psk_export_ui) @@ -50,6 +52,8 @@ else: from .psk import writer as psk_writer from .psk import builder as psk_builder from .psk import importer as psk_importer + from .psk import properties as psk_properties + from .psk import ui as psk_ui from .psk.export import properties as psk_export_properties from .psk.export import operators as psk_export_operators from .psk.export import ui as psk_export_ui @@ -72,6 +76,8 @@ import bpy from bpy.props import PointerProperty classes = psx_types.classes +\ + psk_properties.classes +\ + psk_ui.classes +\ psk_import_operators.classes +\ psk_export_properties.classes +\ psk_export_operators.classes +\ @@ -107,6 +113,7 @@ def register(): bpy.types.TOPBAR_MT_file_import.append(psk_import_menu_func) bpy.types.TOPBAR_MT_file_export.append(psa_export_menu_func) bpy.types.TOPBAR_MT_file_import.append(psa_import_menu_func) + bpy.types.Material.psk = PointerProperty(type=psk_properties.PSX_PG_material) bpy.types.Scene.psa_import = PointerProperty(type=psa_import_properties.PSA_PG_import) bpy.types.Scene.psa_export = PointerProperty(type=psa_export_properties.PSA_PG_export) bpy.types.Scene.psk_export = PointerProperty(type=psk_export_properties.PSK_PG_export) @@ -114,6 +121,7 @@ def register(): def unregister(): + del bpy.types.Material.psk del bpy.types.Scene.psa_import del bpy.types.Scene.psa_export del bpy.types.Scene.psk_export diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index 081be45..2ba6c34 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -3,9 +3,10 @@ from typing import Optional import bmesh import bpy import numpy as np -from bpy.types import Armature +from bpy.types import Armature, Material from .data import * +from .properties import get_poly_flags from ..helpers import * @@ -20,7 +21,7 @@ class PskBuildOptions(object): self.bone_filter_mode = 'ALL' self.bone_collection_indices: List[int] = [] self.use_raw_mesh_data = True - self.material_names: List[str] = [] + self.materials: List[Material] = [] self.should_enforce_bone_name_restrictions = False @@ -138,19 +139,21 @@ def build_psk(context, options: PskBuildOptions) -> PskBuildResult: psk.bones.append(psk_bone) # MATERIALS - material_names = options.material_names - - for material_name in material_names: + for material in options.materials: psk_material = Psk.Material() try: - psk_material.name = bytes(material_name, encoding='windows-1252') + psk_material.name = bytes(material.name, encoding='windows-1252') except UnicodeEncodeError: - raise RuntimeError(f'Material name "{material_name}" contains characters that cannot be encoded in the Windows-1252 codepage') + raise RuntimeError(f'Material name "{material.name}" contains characters that cannot be encoded in the Windows-1252 codepage') psk_material.texture_index = len(psk.materials) + psk_material.poly_flags = get_poly_flags(material.psk) + print(psk_material.name, psk_material.poly_flags) psk.materials.append(psk_material) context.window_manager.progress_begin(0, len(input_objects.mesh_objects)) + material_names = [m.name for m in options.materials] + for object_index, input_mesh_object in enumerate(input_objects.mesh_objects): should_flip_normals = False diff --git a/io_scene_psk_psa/psk/export/operators.py b/io_scene_psk_psa/psk/export/operators.py index a902b27..98d8f4f 100644 --- a/io_scene_psk_psa/psk/export/operators.py +++ b/io_scene_psk_psa/psk/export/operators.py @@ -20,19 +20,19 @@ def is_bone_filter_mode_item_available(context, identifier): def populate_material_list(mesh_objects, material_list): material_list.clear() - material_names = [] + materials = [] for mesh_object in mesh_objects: for i, material_slot in enumerate(mesh_object.material_slots): material = material_slot.material # TODO: put this in the poll arg? if material is None: raise RuntimeError('Material slot cannot be empty (index ' + str(i) + ')') - if material.name not in material_names: - material_names.append(material.name) + if material not in materials: + materials.append(material) - for index, material_name in enumerate(material_names): + for index, material in enumerate(materials): m = material_list.add() - m.material_name = material_name + m.material = material m.index = index @@ -159,9 +159,9 @@ class PSK_OT_export(Operator, ExportHelper): options.bone_filter_mode = pg.bone_filter_mode options.bone_collection_indices = [x.index for x in pg.bone_collection_list if x.is_selected] options.use_raw_mesh_data = pg.use_raw_mesh_data - options.material_names = [m.material_name for m in pg.material_list] + options.materials = [m.material for m in pg.material_list] options.should_enforce_bone_name_restrictions = pg.should_enforce_bone_name_restrictions - + try: result = build_psk(context, options) for warning in result.warnings: diff --git a/io_scene_psk_psa/psk/export/properties.py b/io_scene_psk_psa/psk/export/properties.py index 8382d01..bdb6493 100644 --- a/io_scene_psk_psa/psk/export/properties.py +++ b/io_scene_psk_psa/psk/export/properties.py @@ -1,11 +1,11 @@ -from bpy.props import EnumProperty, CollectionProperty, IntProperty, BoolProperty, StringProperty -from bpy.types import PropertyGroup +from bpy.props import EnumProperty, CollectionProperty, IntProperty, BoolProperty, PointerProperty +from bpy.types import PropertyGroup, Material from ...types import PSX_PG_bone_collection_list_item class PSK_PG_material_list_item(PropertyGroup): - material_name: StringProperty() + material: PointerProperty(type=Material) index: IntProperty() diff --git a/io_scene_psk_psa/psk/export/ui.py b/io_scene_psk_psa/psk/export/ui.py index 6a7a056..4fa55af 100644 --- a/io_scene_psk_psa/psk/export/ui.py +++ b/io_scene_psk_psa/psk/export/ui.py @@ -4,7 +4,7 @@ from bpy.types import UIList class PSK_UL_materials(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): row = layout.row() - row.label(text=str(getattr(item, 'material_name')), icon='MATERIAL') + row.prop(item.material, 'name', text='', emboss=False, icon_value=layout.icon(item.material)) classes = ( diff --git a/io_scene_psk_psa/psk/properties.py b/io_scene_psk_psa/psk/properties.py new file mode 100644 index 0000000..2719336 --- /dev/null +++ b/io_scene_psk_psa/psk/properties.py @@ -0,0 +1,38 @@ +from bpy.props import EnumProperty +from bpy.types import PropertyGroup + +mesh_triangle_types_items = ( + ('MTT_Normal', 'Normal', 'Normal one-sided', 0), + ('MTT_NormalTwoSided', 'Normal Two-Sided', 'Normal but two-sided', 1), + ('MTT_Translucent', 'Translucent', 'Translucent two-sided', 2), + ('MTT_Masked', 'Masked', 'Masked two-sided', 3), + ('MTT_Modulate', 'Modulate', 'Modulation blended two-sided', 4), + ('MTT_Placeholder', 'Placeholder', 'Placeholder triangle for positioning weapon. Invisible', 8), +) + +mesh_triangle_bit_flags_items = ( + ('MTT_Unlit', 'Unlit', 'Full brightness, no lighting', 16), + ('MTT_Flat', 'Flat', 'Flat surface, don\'t do bMeshCurvy thing', 32), + ('MTT_Environment', 'Environment', 'Environment mapped', 64), + ('MTT_NoSmooth', 'No Smooth', 'No bilinear filtering on this poly\'s texture', 128), +) + +class PSX_PG_material(PropertyGroup): + mesh_triangle_type: EnumProperty(items=mesh_triangle_types_items, name='Triangle Type') + mesh_triangle_bit_flags: EnumProperty(items=mesh_triangle_bit_flags_items, name='Triangle Bit Flags', + options={'ENUM_FLAG'}) + +mesh_triangle_types_items_dict = {item[0]: item[3] for item in mesh_triangle_types_items} +mesh_triangle_bit_flags_items_dict = {item[0]: item[3] for item in mesh_triangle_bit_flags_items} + + +def get_poly_flags(material: PSX_PG_material) -> int: + poly_flags = 0 + poly_flags |= mesh_triangle_types_items_dict[material.mesh_triangle_type] + for flag in material.mesh_triangle_bit_flags: + poly_flags |= mesh_triangle_bit_flags_items_dict[flag] + return poly_flags + +classes = ( + PSX_PG_material, +) diff --git a/io_scene_psk_psa/psk/ui.py b/io_scene_psk_psa/psk/ui.py new file mode 100644 index 0000000..a3577b0 --- /dev/null +++ b/io_scene_psk_psa/psk/ui.py @@ -0,0 +1,28 @@ +from bpy.types import Panel + + +class PSK_PT_material(Panel): + bl_label = 'PSK Material' + bl_idname = 'PSK_PT_material' + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = 'material' + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return context.material is not None + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + material = context.material + layout.prop(material.psk, 'mesh_triangle_type') + col = layout.column() + col.prop(material.psk, 'mesh_triangle_bit_flags', expand=True, text='Flags') + + +classes = ( + PSK_PT_material, +) diff --git a/io_scene_psk_psa/types.py b/io_scene_psk_psa/types.py index ae3e9a9..4f13902 100644 --- a/io_scene_psk_psa/types.py +++ b/io_scene_psk_psa/types.py @@ -51,5 +51,5 @@ classes = ( PSX_PG_action_export, PSX_PG_bone_collection_list_item, PSX_UL_bone_collection_list, - PSX_PT_action + PSX_PT_action, ) From 14116963bbf453cb66f9a5e6cc00c2dc33288918 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Wed, 28 Feb 2024 23:21:31 -0800 Subject: [PATCH 3/6] Incremented version to 6.2.0 --- io_scene_psk_psa/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_psk_psa/__init__.py b/io_scene_psk_psa/__init__.py index 4076dd9..a5f5c6a 100644 --- a/io_scene_psk_psa/__init__.py +++ b/io_scene_psk_psa/__init__.py @@ -3,7 +3,7 @@ from bpy.app.handlers import persistent bl_info = { 'name': 'PSK/PSA Importer/Exporter', 'author': 'Colin Basnett, Yurii Ti', - 'version': (6, 1, 2), + 'version': (6, 2, 0), 'blender': (4, 0, 0), 'description': 'PSK/PSA Import/Export (.psk/.psa)', 'warning': '', From 15e2c6ccdded3fbd2c506754423ef0f1ecf23efc Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Thu, 29 Feb 2024 00:32:42 -0800 Subject: [PATCH 4/6] Importing PSKs with poly flags now works --- io_scene_psk_psa/psk/builder.py | 6 ++--- io_scene_psk_psa/psk/importer.py | 4 ++++ io_scene_psk_psa/psk/properties.py | 38 +++++++++++++++++++----------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index 2ba6c34..4ab52d5 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -6,7 +6,7 @@ import numpy as np from bpy.types import Armature, Material from .data import * -from .properties import get_poly_flags +from .properties import triangle_type_and_bit_flags_to_poly_flags from ..helpers import * @@ -146,8 +146,8 @@ def build_psk(context, options: PskBuildOptions) -> PskBuildResult: except UnicodeEncodeError: raise RuntimeError(f'Material name "{material.name}" contains characters that cannot be encoded in the Windows-1252 codepage') psk_material.texture_index = len(psk.materials) - psk_material.poly_flags = get_poly_flags(material.psk) - print(psk_material.name, psk_material.poly_flags) + psk_material.poly_flags = triangle_type_and_bit_flags_to_poly_flags(material.psk.mesh_triangle_type, + material.psk.mesh_triangle_bit_flags) psk.materials.append(psk_material) context.window_manager.progress_begin(0, len(input_objects.mesh_objects)) diff --git a/io_scene_psk_psa/psk/importer.py b/io_scene_psk_psa/psk/importer.py index c00ff07..e9e6e50 100644 --- a/io_scene_psk_psa/psk/importer.py +++ b/io_scene_psk_psa/psk/importer.py @@ -7,6 +7,7 @@ from bpy.types import VertexGroup from mathutils import Quaternion, Vector, Matrix from .data import Psk +from .properties import poly_flags_to_triangle_type_and_bit_flags from ..helpers import rgb_to_srgb, is_bdk_addon_loaded @@ -134,6 +135,9 @@ def import_psk(psk: Psk, context, options: PskImportOptions) -> PskImportResult: else: # Just create a blank material. material = bpy.data.materials.new(material_name) + mesh_triangle_type, mesh_triangle_bit_flags = poly_flags_to_triangle_type_and_bit_flags(psk_material.poly_flags) + material.psk.mesh_triangle_type = mesh_triangle_type + material.psk.mesh_triangle_bit_flags = mesh_triangle_bit_flags material.use_nodes = True mesh_data.materials.append(material) diff --git a/io_scene_psk_psa/psk/properties.py b/io_scene_psk_psa/psk/properties.py index 2719336..917f390 100644 --- a/io_scene_psk_psa/psk/properties.py +++ b/io_scene_psk_psa/psk/properties.py @@ -2,19 +2,19 @@ from bpy.props import EnumProperty from bpy.types import PropertyGroup mesh_triangle_types_items = ( - ('MTT_Normal', 'Normal', 'Normal one-sided', 0), - ('MTT_NormalTwoSided', 'Normal Two-Sided', 'Normal but two-sided', 1), - ('MTT_Translucent', 'Translucent', 'Translucent two-sided', 2), - ('MTT_Masked', 'Masked', 'Masked two-sided', 3), - ('MTT_Modulate', 'Modulate', 'Modulation blended two-sided', 4), - ('MTT_Placeholder', 'Placeholder', 'Placeholder triangle for positioning weapon. Invisible', 8), + ('NORMAL', 'Normal', 'Normal one-sided', 0), + ('NORMAL_TWO_SIDED', 'Normal Two-Sided', 'Normal but two-sided', 1), + ('TRANSLUCENT', 'Translucent', 'Translucent two-sided', 2), + ('MASKED', 'Masked', 'Masked two-sided', 3), + ('MODULATE', 'Modulate', 'Modulation blended two-sided', 4), + ('PLACEHOLDER', 'Placeholder', 'Placeholder triangle for positioning weapon. Invisible', 8), ) mesh_triangle_bit_flags_items = ( - ('MTT_Unlit', 'Unlit', 'Full brightness, no lighting', 16), - ('MTT_Flat', 'Flat', 'Flat surface, don\'t do bMeshCurvy thing', 32), - ('MTT_Environment', 'Environment', 'Environment mapped', 64), - ('MTT_NoSmooth', 'No Smooth', 'No bilinear filtering on this poly\'s texture', 128), + ('UNLIT', 'Unlit', 'Full brightness, no lighting', 16), + ('FLAT', 'Flat', 'Flat surface, don\'t do bMeshCurvy thing', 32), + ('ENVIRONMENT', 'Environment', 'Environment mapped', 64), + ('NO_SMOOTH', 'No Smooth', 'No bilinear filtering on this poly\'s texture', 128), ) class PSX_PG_material(PropertyGroup): @@ -26,13 +26,23 @@ mesh_triangle_types_items_dict = {item[0]: item[3] for item in mesh_triangle_typ mesh_triangle_bit_flags_items_dict = {item[0]: item[3] for item in mesh_triangle_bit_flags_items} -def get_poly_flags(material: PSX_PG_material) -> int: +def triangle_type_and_bit_flags_to_poly_flags(mesh_triangle_type: str, mesh_triangle_bit_flags: set[str]) -> int: poly_flags = 0 - poly_flags |= mesh_triangle_types_items_dict[material.mesh_triangle_type] - for flag in material.mesh_triangle_bit_flags: - poly_flags |= mesh_triangle_bit_flags_items_dict[flag] + poly_flags |= mesh_triangle_types_items_dict.get(mesh_triangle_type, 0) + for flag in mesh_triangle_bit_flags: + poly_flags |= mesh_triangle_bit_flags_items_dict.get(flag, 0) return poly_flags + +def poly_flags_to_triangle_type_and_bit_flags(poly_flags: int) -> (str, set[str]): + try: + triangle_type = next(item[0] for item in mesh_triangle_types_items if item[3] == (poly_flags & 15)) + except StopIteration: + triangle_type = 'NORMAL' + triangle_bit_flags = {item[0] for item in mesh_triangle_bit_flags_items if item[3] & poly_flags} + return triangle_type, triangle_bit_flags + + classes = ( PSX_PG_material, ) From 4d41f1af834d2f49953419379ad759b3ef9345eb Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Fri, 1 Mar 2024 15:14:37 -0800 Subject: [PATCH 5/6] When exporting PSKs, armatures with no bones are now more sensibly handled umodel, for some reason, exports some models with no bones. For compatibility and convenience, an armature with no bones may as well not exist, so we treat it as though it doesn't on export, and a single fake root bone is added for maximum compatibility. --- io_scene_psk_psa/psk/builder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index 4ab52d5..05d1654 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -76,9 +76,9 @@ def build_psk(context, options: PskBuildOptions) -> PskBuildResult: psk = Psk() bones = [] - if armature_object is None: - # If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement - # that a PSK file must have at least one bone. + 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. psk_bone = Psk.Bone() psk_bone.name = bytes('root', encoding='windows-1252') psk_bone.flags = 0 From d81477673bf29959aa7d07d6635805e7cddec104 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Sat, 2 Mar 2024 13:15:48 -0800 Subject: [PATCH 6/6] Fixed a script reload issue --- io_scene_psk_psa/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/io_scene_psk_psa/__init__.py b/io_scene_psk_psa/__init__.py index a5f5c6a..51a7a51 100644 --- a/io_scene_psk_psa/__init__.py +++ b/io_scene_psk_psa/__init__.py @@ -36,6 +36,7 @@ if 'bpy' in locals(): importlib.reload(psa_reader) importlib.reload(psa_writer) importlib.reload(psa_builder) + importlib.reload(psa_importer) importlib.reload(psa_export_properties) importlib.reload(psa_export_operators) importlib.reload(psa_export_ui)