Added the ability to filter bone collections from PSK collection exporter

This commit is contained in:
Colin Basnett
2024-11-29 19:26:23 -08:00
parent 526df424e3
commit 77dc4e5d50
7 changed files with 114 additions and 46 deletions

View File

@@ -6,6 +6,8 @@ if 'bpy' in locals():
importlib.reload(shared_data) importlib.reload(shared_data)
importlib.reload(shared_helpers) importlib.reload(shared_helpers)
importlib.reload(shared_types) importlib.reload(shared_types)
importlib.reload(shared_dfs)
importlib.reload(shared_ui)
importlib.reload(psk_data) importlib.reload(psk_data)
importlib.reload(psk_reader) importlib.reload(psk_reader)
@@ -33,6 +35,7 @@ if 'bpy' in locals():
importlib.reload(psa_import_ui) importlib.reload(psa_import_ui)
else: else:
from .shared import data as shared_data, types as shared_types, helpers as shared_helpers from .shared import data as shared_data, types as shared_types, helpers as shared_helpers
from .shared import dfs as shared_dfs, ui as shared_ui
from .psk import data as psk_data, builder as psk_builder, writer as psk_writer, \ from .psk import data as psk_data, builder as psk_builder, writer as psk_writer, \
importer as psk_importer, properties as psk_properties importer as psk_importer, properties as psk_properties
from .psk import reader as psk_reader, ui as psk_ui from .psk import reader as psk_reader, ui as psk_ui

View File

@@ -13,6 +13,7 @@ from .properties import PSA_PG_export, PSA_PG_export_action_list_item, filter_se
from ..builder import build_psa, PsaBuildSequence, PsaBuildOptions from ..builder import build_psa, PsaBuildSequence, PsaBuildOptions
from ..writer import write_psa from ..writer import write_psa
from ...shared.helpers import populate_bone_collection_list, get_nla_strips_in_frame_range from ...shared.helpers import populate_bone_collection_list, get_nla_strips_in_frame_range
from ...shared.ui import draw_bone_filter_mode
def is_action_for_armature(armature: Armature, action: Action): def is_action_for_armature(armature: Armature, action: Action):
@@ -120,14 +121,6 @@ def get_animation_data_object(context: Context) -> Object:
return animation_data_object return animation_data_object
def is_bone_filter_mode_item_available(context, identifier):
if identifier == 'BONE_COLLECTIONS':
armature = context.active_object.data
if len(armature.collections) == 0:
return False
return True
def get_timeline_marker_sequence_frame_ranges(animation_data: AnimData, context: Context, marker_names: List[str]) -> Dict: def get_timeline_marker_sequence_frame_ranges(animation_data: AnimData, context: Context, marker_names: List[str]) -> Dict:
# Timeline markers need to be sorted so that we can determine the sequence start and end positions. # Timeline markers need to be sorted so that we can determine the sequence start and end positions.
sequence_frame_ranges = dict() sequence_frame_ranges = dict()
@@ -268,7 +261,7 @@ class PSA_OT_export(Operator, ExportHelper):
propname, active_propname = get_sequences_propnames_from_source(pg.sequence_source) propname, active_propname = get_sequences_propnames_from_source(pg.sequence_source)
sequences_panel.template_list(PSA_UL_export_sequences.bl_idname, '', pg, propname, pg, active_propname, sequences_panel.template_list(PSA_UL_export_sequences.bl_idname, '', pg, propname, pg, active_propname,
rows=max(3, min(len(getattr(pg, propname), 10)))) rows=max(3, min(len(getattr(pg, propname)), 10)))
flow = sequences_panel.grid_flow() flow = sequences_panel.grid_flow()
flow.use_property_split = True flow.use_property_split = True
@@ -295,7 +288,8 @@ class PSA_OT_export(Operator, ExportHelper):
bones_header.label(text='Bones', icon='BONE_DATA') bones_header.label(text='Bones', icon='BONE_DATA')
if bones_panel: if bones_panel:
row = bones_panel.row(align=True) row = bones_panel.row(align=True)
row.prop(pg, 'bone_filter_mode', text='Bones')
draw_bone_filter_mode(row, pg)
if pg.bone_filter_mode == 'BONE_COLLECTIONS': if pg.bone_filter_mode == 'BONE_COLLECTIONS':
row = bones_panel.row(align=True) row = bones_panel.row(align=True)

View File

@@ -7,6 +7,7 @@ from bpy.props import BoolProperty, PointerProperty, EnumProperty, FloatProperty
StringProperty StringProperty
from bpy.types import PropertyGroup, Object, Action, AnimData, Context from bpy.types import PropertyGroup, Object, Action, AnimData, Context
from ...shared.data import bone_filter_mode_items
from ...shared.types import PSX_PG_bone_collection_list_item from ...shared.types import PSX_PG_bone_collection_list_item
@@ -155,11 +156,7 @@ class PSA_PG_export(PropertyGroup):
name='Bone Filter', name='Bone Filter',
options=empty_set, options=empty_set,
description='', description='',
items=( items=bone_filter_mode_items,
('ALL', 'All', 'All bones will be exported.'),
('BONE_COLLECTIONS', 'Bone Collections', 'Only bones belonging to the selected bone collections and their '
'ancestors will be exported.'),
)
) )
bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item) bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item)
bone_collection_list_index: IntProperty(default=0, name='', description='') bone_collection_list_index: IntProperty(default=0, name='', description='')

View File

@@ -1,24 +1,18 @@
from typing import List from typing import List, Optional, cast
import bpy import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty, CollectionProperty, IntProperty
from bpy.types import Operator, Context, Object from bpy.types import Operator, Context, Object, Collection, SpaceProperties
from bpy_extras.io_utils import ExportHelper from bpy_extras.io_utils import ExportHelper
from .properties import object_eval_state_items, export_space_items from .properties import object_eval_state_items, export_space_items
from ..builder import build_psk, PskBuildOptions, get_psk_input_objects_for_context, \ from ..builder import build_psk, PskBuildOptions, get_psk_input_objects_for_context, \
get_psk_input_objects_for_collection get_psk_input_objects_for_collection
from ..writer import write_psk from ..writer import write_psk
from ...shared.data import bone_filter_mode_items
from ...shared.helpers import populate_bone_collection_list from ...shared.helpers import populate_bone_collection_list
from ...shared.types import PSX_PG_bone_collection_list_item
from ...shared.ui import draw_bone_filter_mode
def is_bone_filter_mode_item_available(context, identifier):
input_objects = get_psk_input_objects_for_context(context)
armature_object = input_objects.armature_object
if identifier == 'BONE_COLLECTIONS':
if armature_object is None or armature_object.data is None or len(armature_object.data.collections) == 0:
return False
return True
def get_materials_for_mesh_objects(mesh_objects: List[Object]): def get_materials_for_mesh_objects(mesh_objects: List[Object]):
@@ -42,6 +36,49 @@ def populate_material_list(mesh_objects, material_list):
m.index = index m.index = index
def get_collection_from_context(context: Context) -> Optional[Collection]:
if context.space_data.type != 'PROPERTIES':
return None
space_data = cast(SpaceProperties, context.space_data)
if space_data.use_pin_id:
return cast(Collection, space_data.pin_id)
else:
return context.collection
def get_collection_export_operator_from_context(context: Context) -> Optional[object]:
collection = get_collection_from_context(context)
if collection is None:
return None
if 0 > collection.active_exporter_index >= len(collection.exporters):
return None
exporter = collection.exporters[collection.active_exporter_index]
# TODO: make sure this is actually an ASE exporter.
return exporter.export_properties
class PSK_OT_populate_bone_collection_list(Operator):
bl_idname = 'psk_export.populate_bone_collection_list'
bl_label = 'Populate Bone Collection List'
bl_description = 'Populate the bone collection list from the armature that will be used in this collection export'
bl_options = {'INTERNAL'}
def execute(self, context):
export_operator = get_collection_export_operator_from_context(context)
if export_operator is None:
self.report({'ERROR_INVALID_CONTEXT'}, 'No valid export operator found in context')
return {'CANCELLED'}
input_objects = get_psk_input_objects_for_collection(context.collection)
if input_objects.armature_object is None:
self.report({'ERROR_INVALID_CONTEXT'}, 'No armature found in collection')
return {'CANCELLED'}
populate_bone_collection_list(input_objects.armature_object, export_operator.bone_collection_list)
return {'FINISHED'}
class PSK_OT_material_list_move_up(Operator): class PSK_OT_material_list_move_up(Operator):
bl_idname = 'psk_export.material_list_item_move_up' bl_idname = 'psk_export.material_list_item_move_up'
bl_label = 'Move Up' bl_label = 'Move Up'
@@ -78,6 +115,9 @@ class PSK_OT_material_list_move_down(Operator):
return {'FINISHED'} return {'FINISHED'}
empty_set = set()
class PSK_OT_export_collection(Operator, ExportHelper): class PSK_OT_export_collection(Operator, ExportHelper):
bl_idname = 'export.psk_collection' bl_idname = 'export.psk_collection'
bl_label = 'Export' bl_label = 'Export'
@@ -121,6 +161,14 @@ class PSK_OT_export_collection(Operator, ExportHelper):
items=export_space_items, items=export_space_items,
default='WORLD' default='WORLD'
) )
bone_filter_mode: EnumProperty(
name='Bone Filter',
options=empty_set,
description='',
items=bone_filter_mode_items,
)
bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item)
bone_collection_list_index: IntProperty(default=0)
def execute(self, context): def execute(self, context):
@@ -133,12 +181,13 @@ class PSK_OT_export_collection(Operator, ExportHelper):
return {'CANCELLED'} return {'CANCELLED'}
options = PskBuildOptions() options = PskBuildOptions()
options.bone_filter_mode = 'ALL'
options.object_eval_state = self.object_eval_state options.object_eval_state = self.object_eval_state
options.materials = get_materials_for_mesh_objects([x.obj for x in input_objects.mesh_objects]) options.materials = get_materials_for_mesh_objects([x.obj for x in input_objects.mesh_objects])
options.should_enforce_bone_name_restrictions = self.should_enforce_bone_name_restrictions options.should_enforce_bone_name_restrictions = self.should_enforce_bone_name_restrictions
options.scale = self.scale options.scale = self.scale
options.export_space = self.export_space options.export_space = self.export_space
options.bone_filter_mode = self.bone_filter_mode
options.bone_collection_indices = [x.index for x in self.bone_collection_list if x.is_selected]
try: try:
result = build_psk(context, input_objects, options) result = build_psk(context, input_objects, options)
@@ -179,10 +228,17 @@ class PSK_OT_export_collection(Operator, ExportHelper):
bones_header, bones_panel = layout.panel('Bones', default_closed=False) bones_header, bones_panel = layout.panel('Bones', default_closed=False)
bones_header.label(text='Bones', icon='BONE_DATA') bones_header.label(text='Bones', icon='BONE_DATA')
if bones_panel: if bones_panel:
flow = bones_panel.grid_flow(row_major=True) draw_bone_filter_mode(bones_panel, self)
flow.use_property_split = True
flow.use_property_decorate = False row = bones_panel.row(align=True)
flow.prop(self, 'should_enforce_bone_name_restrictions')
if self.bone_filter_mode == 'BONE_COLLECTIONS':
rows = max(3, min(len(self.bone_collection_list), 10))
row.template_list('PSX_UL_bone_collection_list', '', self, 'bone_collection_list', self, 'bone_collection_list_index', rows=rows)
col = row.column()
col.operator(PSK_OT_populate_bone_collection_list.bl_idname, text='', icon='FILE_REFRESH')
bones_panel.prop(self, 'should_enforce_bone_name_restrictions')
class PSK_OT_export(Operator, ExportHelper): class PSK_OT_export(Operator, ExportHelper):
@@ -215,7 +271,7 @@ class PSK_OT_export(Operator, ExportHelper):
populate_bone_collection_list(input_objects.armature_object, pg.bone_collection_list) populate_bone_collection_list(input_objects.armature_object, pg.bone_collection_list)
try: try:
populate_material_list([x[0] for x in input_objects.mesh_objects], pg.material_list) populate_material_list([x.obj for x in input_objects.mesh_objects], pg.material_list)
except RuntimeError as e: except RuntimeError as e:
self.report({'ERROR_INVALID_CONTEXT'}, str(e)) self.report({'ERROR_INVALID_CONTEXT'}, str(e))
return {'CANCELLED'} return {'CANCELLED'}
@@ -242,14 +298,7 @@ class PSK_OT_export(Operator, ExportHelper):
bones_header, bones_panel = layout.panel('Bones', default_closed=False) bones_header, bones_panel = layout.panel('Bones', default_closed=False)
bones_header.label(text='Bones', icon='BONE_DATA') bones_header.label(text='Bones', icon='BONE_DATA')
if bones_panel: if bones_panel:
bone_filter_mode_items = pg.bl_rna.properties['bone_filter_mode'].enum_items_static draw_bone_filter_mode(bones_panel, pg)
row = bones_panel.row(align=True)
for item in bone_filter_mode_items:
identifier = item.identifier
item_layout = row.row(align=True)
item_layout.prop_enum(pg, 'bone_filter_mode', item.identifier)
item_layout.enabled = is_bone_filter_mode_item_available(context, identifier)
if pg.bone_filter_mode == 'BONE_COLLECTIONS': if pg.bone_filter_mode == 'BONE_COLLECTIONS':
row = bones_panel.row() row = bones_panel.row()
rows = max(3, min(len(pg.bone_collection_list), 10)) rows = max(3, min(len(pg.bone_collection_list), 10))
@@ -303,4 +352,5 @@ classes = (
PSK_OT_material_list_move_down, PSK_OT_material_list_move_down,
PSK_OT_export, PSK_OT_export,
PSK_OT_export_collection, PSK_OT_export_collection,
PSK_OT_populate_bone_collection_list,
) )

View File

@@ -26,11 +26,7 @@ class PSK_PG_export(PropertyGroup):
name='Bone Filter', name='Bone Filter',
options=empty_set, options=empty_set,
description='', description='',
items=( items=bone_filter_mode_items
('ALL', 'All', 'All bones will be exported'),
('BONE_COLLECTIONS', 'Bone Collections',
'Only bones belonging to the selected bone collections and their ancestors will be exported')
)
) )
bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item) bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item)
bone_collection_list_index: IntProperty(default=0) bone_collection_list_index: IntProperty(default=0)

View File

@@ -93,3 +93,9 @@ class Section(Structure):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
super().__init__(*args, **kw) super().__init__(*args, **kw)
self.type_flags = 1999801 self.type_flags = 1999801
bone_filter_mode_items = (
('ALL', 'All', 'All bones will be exported'),
('BONE_COLLECTIONS', 'Bone Collections', 'Only bones belonging to the selected bone collections and their ancestors will be exported')
)

View File

@@ -0,0 +1,22 @@
from bpy.types import UILayout
from .data import bone_filter_mode_items
def is_bone_filter_mode_item_available(pg, identifier):
match identifier:
case 'BONE_COLLECTIONS':
if len(pg.bone_collection_list) == 0:
return False
case _:
pass
return True
def draw_bone_filter_mode(layout: UILayout, pg):
row = layout.row(align=True)
for item_identifier, _, _ in bone_filter_mode_items:
identifier = item_identifier
item_layout = row.row(align=True)
item_layout.prop_enum(pg, 'bone_filter_mode', item_identifier)
item_layout.enabled = is_bone_filter_mode_item_available(pg, identifier)