Implemented #22: Allow users to change the exported PSK material order
There is now a material menu that the user can re-order in the PSK export dialog.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user