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 collections import OrderedDict
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from .data import *
|
from .data import *
|
||||||
from ..helpers import *
|
from ..helpers import *
|
||||||
@@ -15,8 +16,9 @@ class PskInputObjects(object):
|
|||||||
class PskBuildOptions(object):
|
class PskBuildOptions(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bone_filter_mode = 'ALL'
|
self.bone_filter_mode = 'ALL'
|
||||||
self.bone_group_indices = []
|
self.bone_group_indices: List[int] = []
|
||||||
self.use_raw_mesh_data = True
|
self.use_raw_mesh_data = True
|
||||||
|
self.material_names: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
def get_psk_input_objects(context) -> PskInputObjects:
|
def get_psk_input_objects(context) -> PskInputObjects:
|
||||||
@@ -62,7 +64,6 @@ def build_psk(context, options: PskBuildOptions) -> Psk:
|
|||||||
|
|
||||||
psk = Psk()
|
psk = Psk()
|
||||||
bones = []
|
bones = []
|
||||||
materials = OrderedDict()
|
|
||||||
|
|
||||||
if armature_object is None:
|
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
|
# 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_bone.parent_index = parent_index
|
||||||
psk.bones[parent_index].children_count += 1
|
psk.bones[parent_index].children_count += 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
psk_bone.parent_index = 0
|
psk_bone.parent_index = -1
|
||||||
|
|
||||||
if bone.parent is not None:
|
if bone.parent is not None:
|
||||||
rotation = bone.matrix.to_quaternion().conjugated()
|
rotation = bone.matrix.to_quaternion().conjugated()
|
||||||
@@ -102,40 +103,35 @@ def build_psk(context, options: PskBuildOptions) -> Psk:
|
|||||||
parent_tail = quat_parent @ bone.parent.tail
|
parent_tail = quat_parent @ bone.parent.tail
|
||||||
location = (parent_tail - parent_head) + bone.head
|
location = (parent_tail - parent_head) + bone.head
|
||||||
else:
|
else:
|
||||||
location = armature_object.matrix_local @ bone.head
|
local_matrix = armature_object.matrix_local
|
||||||
rot_matrix = bone.matrix @ armature_object.matrix_local.to_3x3()
|
location = local_matrix @ bone.head
|
||||||
|
rot_matrix = bone.matrix @ local_matrix.to_3x3()
|
||||||
rotation = rot_matrix.to_quaternion()
|
rotation = rot_matrix.to_quaternion()
|
||||||
|
|
||||||
psk_bone.location.x = location.x
|
psk_bone.location.x = location.x
|
||||||
psk_bone.location.y = location.y
|
psk_bone.location.y = location.y
|
||||||
psk_bone.location.z = location.z
|
psk_bone.location.z = location.z
|
||||||
|
|
||||||
|
psk_bone.rotation.w = rotation.w
|
||||||
psk_bone.rotation.x = rotation.x
|
psk_bone.rotation.x = rotation.x
|
||||||
psk_bone.rotation.y = rotation.y
|
psk_bone.rotation.y = rotation.y
|
||||||
psk_bone.rotation.z = rotation.z
|
psk_bone.rotation.z = rotation.z
|
||||||
psk_bone.rotation.w = rotation.w
|
|
||||||
|
|
||||||
psk.bones.append(psk_bone)
|
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:
|
for input_mesh_object in input_objects.mesh_objects:
|
||||||
|
|
||||||
# MATERIALS
|
# MATERIALS
|
||||||
material_indices = []
|
material_indices = [material_names.index(material.name) for material in input_mesh_object.data.materials]
|
||||||
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)
|
|
||||||
|
|
||||||
if options.use_raw_mesh_data:
|
if options.use_raw_mesh_data:
|
||||||
mesh_object = input_mesh_object
|
mesh_object = input_mesh_object
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty
|
from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty, PointerProperty
|
||||||
from bpy.types import Operator, PropertyGroup
|
from bpy.types import Operator, PropertyGroup, UIList, Material
|
||||||
from bpy_extras.io_utils import ExportHelper
|
from bpy_extras.io_utils import ExportHelper
|
||||||
|
|
||||||
from .builder import build_psk, PskBuildOptions, get_psk_input_objects
|
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
|
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):
|
class PskExportOperator(Operator, ExportHelper):
|
||||||
bl_idname = 'export.psk'
|
bl_idname = 'export.psk'
|
||||||
bl_label = 'Export'
|
bl_label = 'Export'
|
||||||
@@ -92,6 +161,7 @@ class PskExportOperator(Operator, ExportHelper):
|
|||||||
|
|
||||||
# Populate bone groups list.
|
# Populate bone groups list.
|
||||||
populate_bone_group_list(input_objects.armature_object, pg.bone_group_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)
|
context.window_manager.fileselect_add(self)
|
||||||
|
|
||||||
@@ -114,10 +184,9 @@ class PskExportOperator(Operator, ExportHelper):
|
|||||||
layout.prop(pg, 'use_raw_mesh_data')
|
layout.prop(pg, 'use_raw_mesh_data')
|
||||||
|
|
||||||
# BONES
|
# BONES
|
||||||
box = layout.box()
|
layout.label(text='Bones', icon='BONE_DATA')
|
||||||
box.label(text='Bones', icon='BONE_DATA')
|
|
||||||
bone_filter_mode_items = pg.bl_rna.properties['bone_filter_mode'].enum_items_static
|
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:
|
for item in bone_filter_mode_items:
|
||||||
identifier = item.identifier
|
identifier = item.identifier
|
||||||
item_layout = row.row(align=True)
|
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)
|
item_layout.enabled = is_bone_filter_mode_item_available(context, identifier)
|
||||||
|
|
||||||
if pg.bone_filter_mode == 'BONE_GROUPS':
|
if pg.bone_filter_mode == 'BONE_GROUPS':
|
||||||
row = box.row()
|
row = layout.row()
|
||||||
rows = max(3, min(len(pg.bone_group_list), 10))
|
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)
|
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):
|
def execute(self, context):
|
||||||
pg = context.scene.psk_export
|
pg = context.scene.psk_export
|
||||||
options = PskBuildOptions()
|
options = PskBuildOptions()
|
||||||
options.bone_filter_mode = pg.bone_filter_mode
|
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.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.use_raw_mesh_data = pg.use_raw_mesh_data
|
||||||
|
options.material_names = [m.material_name for m in pg.material_list]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
psk = build_psk(context, options)
|
psk = build_psk(context, options)
|
||||||
export_psk(psk, self.filepath)
|
export_psk(psk, self.filepath)
|
||||||
@@ -158,9 +240,15 @@ class PskExportPropertyGroup(PropertyGroup):
|
|||||||
bone_group_list: CollectionProperty(type=BoneGroupListItem)
|
bone_group_list: CollectionProperty(type=BoneGroupListItem)
|
||||||
bone_group_list_index: IntProperty(default=0)
|
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')
|
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 = (
|
classes = (
|
||||||
|
MaterialListItem,
|
||||||
|
PSK_UL_MaterialList,
|
||||||
|
PskMaterialListItemMoveUp,
|
||||||
|
PskMaterialListItemMoveDown,
|
||||||
PskExportOperator,
|
PskExportOperator,
|
||||||
PskExportPropertyGroup
|
PskExportPropertyGroup,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user