diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index 7f98655..8a13f5c 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from typing import Dict, List from .data import * from ..helpers import * @@ -15,8 +16,9 @@ class PskInputObjects(object): class PskBuildOptions(object): def __init__(self): self.bone_filter_mode = 'ALL' - self.bone_group_indices = [] + self.bone_group_indices: List[int] = [] self.use_raw_mesh_data = True + self.material_names: List[str] = [] def get_psk_input_objects(context) -> PskInputObjects: @@ -62,7 +64,6 @@ def build_psk(context, options: PskBuildOptions) -> Psk: psk = Psk() bones = [] - materials = OrderedDict() 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 @@ -93,7 +94,7 @@ def build_psk(context, options: PskBuildOptions) -> Psk: psk_bone.parent_index = parent_index psk.bones[parent_index].children_count += 1 except ValueError: - psk_bone.parent_index = 0 + psk_bone.parent_index = -1 if bone.parent is not None: rotation = bone.matrix.to_quaternion().conjugated() @@ -102,40 +103,35 @@ def build_psk(context, options: PskBuildOptions) -> Psk: parent_tail = quat_parent @ bone.parent.tail location = (parent_tail - parent_head) + bone.head else: - location = armature_object.matrix_local @ bone.head - rot_matrix = bone.matrix @ armature_object.matrix_local.to_3x3() + local_matrix = armature_object.matrix_local + location = local_matrix @ bone.head + rot_matrix = bone.matrix @ local_matrix.to_3x3() rotation = rot_matrix.to_quaternion() psk_bone.location.x = location.x psk_bone.location.y = location.y psk_bone.location.z = location.z + psk_bone.rotation.w = rotation.w psk_bone.rotation.x = rotation.x psk_bone.rotation.y = rotation.y psk_bone.rotation.z = rotation.z - psk_bone.rotation.w = rotation.w psk.bones.append(psk_bone) + # MATERIALS + material_names = options.material_names + + for material_name in material_names: + psk_material = Psk.Material() + psk_material.name = bytes(material_name, encoding='windows-1252') + psk_material.texture_index = len(psk.materials) + psk.materials.append(psk_material) + for input_mesh_object in input_objects.mesh_objects: # MATERIALS - material_indices = [] - for i, material in enumerate(input_mesh_object.data.materials): - if material is None: - raise RuntimeError('Material cannot be empty (index ' + str(i) + ')') - if material.name in materials: - # Material already evaluated, just get its index. - material_index = list(materials.keys()).index(material.name) - else: - # New material. - psk_material = Psk.Material() - psk_material.name = bytes(material.name, encoding='windows-1252') - psk_material.texture_index = len(psk.materials) - psk.materials.append(psk_material) - materials[material.name] = material - material_index = psk_material.texture_index - material_indices.append(material_index) + material_indices = [material_names.index(material.name) for material in input_mesh_object.data.materials] if options.use_raw_mesh_data: mesh_object = input_mesh_object diff --git a/io_scene_psk_psa/psk/exporter.py b/io_scene_psk_psa/psk/exporter.py index 5d08d75..f5c1528 100644 --- a/io_scene_psk_psa/psk/exporter.py +++ b/io_scene_psk_psa/psk/exporter.py @@ -1,7 +1,7 @@ from typing import Type -from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty -from bpy.types import Operator, PropertyGroup +from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty, PointerProperty +from bpy.types import Operator, PropertyGroup, UIList, Material from bpy_extras.io_utils import ExportHelper from .builder import build_psk, PskBuildOptions, get_psk_input_objects @@ -67,6 +67,75 @@ def is_bone_filter_mode_item_available(context, identifier): return True +class PSK_UL_MaterialList(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + row = layout.row() + row.label(text=str(item.material_name), icon='MATERIAL') + + +class MaterialListItem(PropertyGroup): + material_name: StringProperty() + index: IntProperty() + + @property + def name(self): + return self.material_name + + +def populate_material_list(mesh_objects, material_list): + material_list.clear() + + material_names = [] + for mesh_object in mesh_objects: + for i, material in enumerate(mesh_object.data.materials): + # TODO: put this in the poll arg? + if material is None: + raise RuntimeError('Material cannot be empty (index ' + str(i) + ')') + if material.name not in material_names: + material_names.append(material.name) + + for index, material_name in enumerate(material_names): + m = material_list.add() + m.material_name = material_name + m.index = index + + +class PskMaterialListItemMoveUp(Operator): + bl_idname = 'psk_export.material_list_item_move_up' + bl_label = 'Move Up' + bl_options = {'INTERNAL'} + bl_description = 'Move the selected material up one slot' + + @classmethod + def poll(cls, context): + pg = context.scene.psk_export + return pg.material_list_index > 0 + + def execute(self, context): + pg = context.scene.psk_export + pg.material_list.move(pg.material_list_index, pg.material_list_index - 1) + pg.material_list_index -= 1 + return {"FINISHED"} + + +class PskMaterialListItemMoveDown(Operator): + bl_idname = 'psk_export.material_list_item_move_down' + bl_label = 'Move Down' + bl_options = {'INTERNAL'} + bl_description = 'Move the selected material down one slot' + + @classmethod + def poll(cls, context): + pg = context.scene.psk_export + return pg.material_list_index < len(pg.material_list) - 1 + + def execute(self, context): + pg = context.scene.psk_export + pg.material_list.move(pg.material_list_index, pg.material_list_index + 1) + pg.material_list_index += 1 + return {"FINISHED"} + + class PskExportOperator(Operator, ExportHelper): bl_idname = 'export.psk' bl_label = 'Export' @@ -92,6 +161,7 @@ class PskExportOperator(Operator, ExportHelper): # Populate bone groups list. populate_bone_group_list(input_objects.armature_object, pg.bone_group_list) + populate_material_list(input_objects.mesh_objects, pg.material_list) context.window_manager.fileselect_add(self) @@ -114,10 +184,9 @@ class PskExportOperator(Operator, ExportHelper): layout.prop(pg, 'use_raw_mesh_data') # BONES - box = layout.box() - box.label(text='Bones', icon='BONE_DATA') + layout.label(text='Bones', icon='BONE_DATA') bone_filter_mode_items = pg.bl_rna.properties['bone_filter_mode'].enum_items_static - row = box.row(align=True) + row = layout.row(align=True) for item in bone_filter_mode_items: identifier = item.identifier item_layout = row.row(align=True) @@ -125,16 +194,29 @@ class PskExportOperator(Operator, ExportHelper): item_layout.enabled = is_bone_filter_mode_item_available(context, identifier) if pg.bone_filter_mode == 'BONE_GROUPS': - row = box.row() + row = layout.row() rows = max(3, min(len(pg.bone_group_list), 10)) row.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows) + layout.separator() + + # MATERIALS + layout.label(text='Materials', icon='MATERIAL') + row = layout.row() + rows = max(3, min(len(pg.bone_group_list), 10)) + row.template_list('PSK_UL_MaterialList', '', pg, 'material_list', pg, 'material_list_index', rows=rows) + col = row.column(align=True) + col.operator(PskMaterialListItemMoveUp.bl_idname, text='', icon='TRIA_UP') + col.operator(PskMaterialListItemMoveDown.bl_idname, text='', icon='TRIA_DOWN') + def execute(self, context): pg = context.scene.psk_export options = PskBuildOptions() options.bone_filter_mode = pg.bone_filter_mode options.bone_group_indices = [x.index for x in pg.bone_group_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] + try: psk = build_psk(context, options) export_psk(psk, self.filepath) @@ -158,9 +240,15 @@ class PskExportPropertyGroup(PropertyGroup): bone_group_list: CollectionProperty(type=BoneGroupListItem) bone_group_list_index: IntProperty(default=0) use_raw_mesh_data: BoolProperty(default=False, name='Raw Mesh Data', description='No modifiers will be evaluated as part of the exported mesh') + material_list: CollectionProperty(type=MaterialListItem) + material_list_index: IntProperty(default=0) classes = ( + MaterialListItem, + PSK_UL_MaterialList, + PskMaterialListItemMoveUp, + PskMaterialListItemMoveDown, PskExportOperator, - PskExportPropertyGroup + PskExportPropertyGroup, )