Fixed a load of PEP8 warnings
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from collections import Counter
|
|
||||||
import re
|
import re
|
||||||
|
from collections import Counter
|
||||||
from typing import List, Iterable
|
from typing import List, Iterable
|
||||||
|
|
||||||
|
import bpy.types
|
||||||
from bpy.types import NlaStrip, Object
|
from bpy.types import NlaStrip, Object
|
||||||
from .types import BoneGroupListItem
|
|
||||||
|
|
||||||
|
|
||||||
class Timer:
|
class Timer:
|
||||||
@@ -47,7 +47,7 @@ def get_nla_strips_in_timeframe(animation_data, frame_min, frame_max) -> List[Nl
|
|||||||
return strips
|
return strips
|
||||||
|
|
||||||
|
|
||||||
def populate_bone_group_list(armature_object: Object, bone_group_list: Iterable[BoneGroupListItem]) -> None:
|
def populate_bone_group_list(armature_object: Object, bone_group_list: bpy.types.Collection) -> None:
|
||||||
"""
|
"""
|
||||||
Updates the bone group collection.
|
Updates the bone group collection.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, Iterable
|
from typing import Dict
|
||||||
|
|
||||||
from bpy.types import Action
|
from bpy.types import Action
|
||||||
|
|
||||||
@@ -217,7 +217,8 @@ def build_psa(context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
# Add prefixes and suffices to the names of the export sequences and strip whitespace.
|
# Add prefixes and suffices to the names of the export sequences and strip whitespace.
|
||||||
for export_sequence in export_sequences:
|
for export_sequence in export_sequences:
|
||||||
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'.strip()
|
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'
|
||||||
|
export_sequence.name = export_sequence.name.strip()
|
||||||
|
|
||||||
# Now build the PSA sequences.
|
# Now build the PSA sequences.
|
||||||
# We actually alter the timeline frame and simply record the resultant pose bone matrices.
|
# We actually alter the timeline frame and simply record the resultant pose bone matrices.
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
from collections import Counter
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
@@ -16,17 +14,19 @@ from ..helpers import *
|
|||||||
from ..types import BoneGroupListItem
|
from ..types import BoneGroupListItem
|
||||||
|
|
||||||
|
|
||||||
|
def write_section(fp, name: bytes, data_type: Type[Structure] = None, data: list = None):
|
||||||
|
section = Section()
|
||||||
|
section.name = name
|
||||||
|
if data_type is not None and data is not None:
|
||||||
|
section.data_size = sizeof(data_type)
|
||||||
|
section.data_count = len(data)
|
||||||
|
fp.write(section)
|
||||||
|
if data is not None:
|
||||||
|
for datum in data:
|
||||||
|
fp.write(datum)
|
||||||
|
|
||||||
|
|
||||||
def export_psa(psa: Psa, path: str):
|
def export_psa(psa: Psa, path: str):
|
||||||
def write_section(fp, name: bytes, data_type: Type[Structure] = None, data: list = None):
|
|
||||||
section = Section()
|
|
||||||
section.name = name
|
|
||||||
if data_type is not None and data is not None:
|
|
||||||
section.data_size = sizeof(data_type)
|
|
||||||
section.data_count = len(data)
|
|
||||||
fp.write(section)
|
|
||||||
if data is not None:
|
|
||||||
for datum in data:
|
|
||||||
fp.write(datum)
|
|
||||||
with open(path, 'wb') as fp:
|
with open(path, 'wb') as fp:
|
||||||
write_section(fp, b'ANIMHEAD')
|
write_section(fp, b'ANIMHEAD')
|
||||||
write_section(fp, b'BONENAMES', Psa.Bone, psa.bones)
|
write_section(fp, b'BONENAMES', Psa.Bone, psa.bones)
|
||||||
@@ -61,16 +61,19 @@ def psa_export_property_group_animation_data_override_poll(_context, obj):
|
|||||||
return obj.animation_data is not None
|
return obj.animation_data is not None
|
||||||
|
|
||||||
|
|
||||||
|
empty_set = set()
|
||||||
|
|
||||||
|
|
||||||
class PsaExportPropertyGroup(PropertyGroup):
|
class PsaExportPropertyGroup(PropertyGroup):
|
||||||
root_motion: BoolProperty(
|
root_motion: BoolProperty(
|
||||||
name='Root Motion',
|
name='Root Motion',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
default=False,
|
default=False,
|
||||||
description='The root bone will be transformed as it appears in the scene',
|
description='The root bone will be transformed as it appears in the scene',
|
||||||
)
|
)
|
||||||
should_override_animation_data: BoolProperty(
|
should_override_animation_data: BoolProperty(
|
||||||
name='Override Animation Data',
|
name='Override Animation Data',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
default=False,
|
default=False,
|
||||||
description='Use the animation data from a different object instead of the selected object'
|
description='Use the animation data from a different object instead of the selected object'
|
||||||
)
|
)
|
||||||
@@ -80,7 +83,7 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
)
|
)
|
||||||
sequence_source: EnumProperty(
|
sequence_source: EnumProperty(
|
||||||
name='Source',
|
name='Source',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='',
|
description='',
|
||||||
items=(
|
items=(
|
||||||
('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
|
('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
|
||||||
@@ -90,7 +93,7 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
)
|
)
|
||||||
fps_source: EnumProperty(
|
fps_source: EnumProperty(
|
||||||
name='FPS Source',
|
name='FPS Source',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='',
|
description='',
|
||||||
items=(
|
items=(
|
||||||
('SCENE', 'Scene', '', 'SCENE_DATA', 0),
|
('SCENE', 'Scene', '', 'SCENE_DATA', 0),
|
||||||
@@ -100,7 +103,7 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
('CUSTOM', 'Custom', '', 2)
|
('CUSTOM', 'Custom', '', 2)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=set(), step=100,
|
fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=empty_set, step=100,
|
||||||
soft_max=60.0)
|
soft_max=60.0)
|
||||||
action_list: CollectionProperty(type=PsaExportActionListItem)
|
action_list: CollectionProperty(type=PsaExportActionListItem)
|
||||||
action_list_index: IntProperty(default=0)
|
action_list_index: IntProperty(default=0)
|
||||||
@@ -108,7 +111,7 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
marker_list_index: IntProperty(default=0)
|
marker_list_index: IntProperty(default=0)
|
||||||
bone_filter_mode: EnumProperty(
|
bone_filter_mode: EnumProperty(
|
||||||
name='Bone Filter',
|
name='Bone Filter',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='',
|
description='',
|
||||||
items=(
|
items=(
|
||||||
('ALL', 'All', 'All bones will be exported.'),
|
('ALL', 'All', 'All bones will be exported.'),
|
||||||
@@ -121,7 +124,7 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
should_use_original_sequence_names: BoolProperty(
|
should_use_original_sequence_names: BoolProperty(
|
||||||
default=False,
|
default=False,
|
||||||
name='Original Names',
|
name='Original Names',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
update=should_use_original_sequence_names_updated,
|
update=should_use_original_sequence_names_updated,
|
||||||
description='If the action was imported from the PSA Import panel, the original name of the sequence will be '
|
description='If the action was imported from the PSA Import panel, the original name of the sequence will be '
|
||||||
'used instead of the Blender action name',
|
'used instead of the Blender action name',
|
||||||
@@ -129,12 +132,12 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
should_trim_timeline_marker_sequences: BoolProperty(
|
should_trim_timeline_marker_sequences: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
name='Trim Sequences',
|
name='Trim Sequences',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Frames without NLA track information at the boundaries of timeline markers will be excluded from '
|
description='Frames without NLA track information at the boundaries of timeline markers will be excluded from '
|
||||||
'the exported sequences '
|
'the exported sequences '
|
||||||
)
|
)
|
||||||
sequence_name_prefix: StringProperty(name='Prefix', options=set())
|
sequence_name_prefix: StringProperty(name='Prefix', options=empty_set)
|
||||||
sequence_name_suffix: StringProperty(name='Suffix', options=set())
|
sequence_name_suffix: StringProperty(name='Suffix', options=empty_set)
|
||||||
sequence_filter_name: StringProperty(
|
sequence_filter_name: StringProperty(
|
||||||
default='',
|
default='',
|
||||||
name='Filter by Name',
|
name='Filter by Name',
|
||||||
@@ -143,14 +146,14 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
sequence_use_filter_invert: BoolProperty(
|
sequence_use_filter_invert: BoolProperty(
|
||||||
default=False,
|
default=False,
|
||||||
name='Invert',
|
name='Invert',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Invert filtering (show hidden items, and vice versa)')
|
description='Invert filtering (show hidden items, and vice versa)')
|
||||||
sequence_filter_asset: BoolProperty(
|
sequence_filter_asset: BoolProperty(
|
||||||
default=False,
|
default=False,
|
||||||
name='Show assets',
|
name='Show assets',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Show actions that belong to an asset library')
|
description='Show actions that belong to an asset library')
|
||||||
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set())
|
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=empty_set)
|
||||||
|
|
||||||
|
|
||||||
def is_bone_filter_mode_item_available(context, identifier):
|
def is_bone_filter_mode_item_available(context, identifier):
|
||||||
@@ -182,13 +185,13 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
try:
|
try:
|
||||||
cls._check_context(context)
|
cls._check_context(context)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
cls.poll_message_set((str(e)))
|
cls.poll_message_set(str(e))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
|
|
||||||
# FPS
|
# FPS
|
||||||
layout.prop(pg, 'fps_source', text='FPS')
|
layout.prop(pg, 'fps_source', text='FPS')
|
||||||
@@ -291,7 +294,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||||
|
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
self.armature = context.view_layer.objects.active
|
self.armature = context.view_layer.objects.active
|
||||||
|
|
||||||
# Populate actions list.
|
# Populate actions list.
|
||||||
@@ -326,7 +329,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
|
|
||||||
actions = [x.action for x in pg.action_list if x.is_selected]
|
actions = [x.action for x in pg.action_list if x.is_selected]
|
||||||
marker_names = [x.name for x in pg.marker_list if x.is_selected]
|
marker_names = [x.name for x in pg.marker_list if x.is_selected]
|
||||||
@@ -349,6 +352,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
psa = build_psa(context, options)
|
psa = build_psa(context, options)
|
||||||
|
self.report({'INFO'}, f'PSA export successful')
|
||||||
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'}
|
||||||
@@ -358,7 +362,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[int]:
|
def filter_sequences(pg: PsaExportPropertyGroup, sequences) -> List[int]:
|
||||||
bitflag_filter_item = 1 << 30
|
bitflag_filter_item = 1 << 30
|
||||||
flt_flags = [bitflag_filter_item] * len(sequences)
|
flt_flags = [bitflag_filter_item] * len(sequences)
|
||||||
|
|
||||||
@@ -381,7 +385,7 @@ def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_c
|
|||||||
return flt_flags
|
return flt_flags
|
||||||
|
|
||||||
|
|
||||||
def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaExportActionListItem]:
|
def get_visible_sequences(pg: PsaExportPropertyGroup, sequences) -> List[PsaExportActionListItem]:
|
||||||
visible_sequences = []
|
visible_sequences = []
|
||||||
for i, flag in enumerate(filter_sequences(pg, sequences)):
|
for i, flag in enumerate(filter_sequences(pg, sequences)):
|
||||||
if bool(flag & (1 << 30)):
|
if bool(flag & (1 << 30)):
|
||||||
@@ -402,7 +406,7 @@ class PSA_UL_ExportSequenceList(UIList):
|
|||||||
layout.label(text='', icon='ASSET_MANAGER')
|
layout.label(text='', icon='ASSET_MANAGER')
|
||||||
|
|
||||||
def draw_filter(self, context, layout):
|
def draw_filter(self, context, layout):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
subrow = row.row(align=True)
|
subrow = row.row(align=True)
|
||||||
subrow.prop(pg, 'sequence_filter_name', text="")
|
subrow.prop(pg, 'sequence_filter_name', text="")
|
||||||
@@ -414,7 +418,7 @@ class PSA_UL_ExportSequenceList(UIList):
|
|||||||
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
|
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
|
||||||
|
|
||||||
def filter_items(self, context, data, prop):
|
def filter_items(self, context, data, prop):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
actions = getattr(data, prop)
|
actions = getattr(data, prop)
|
||||||
flt_flags = filter_sequences(pg, actions)
|
flt_flags = filter_sequences(pg, actions)
|
||||||
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name')
|
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name')
|
||||||
@@ -438,14 +442,14 @@ class PsaExportActionsSelectAll(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
item_list = cls.get_item_list(context)
|
item_list = cls.get_item_list(context)
|
||||||
visible_sequences = get_visible_sequences(pg, item_list)
|
visible_sequences = get_visible_sequences(pg, item_list)
|
||||||
has_unselected_sequences = any(map(lambda item: not item.is_selected, visible_sequences))
|
has_unselected_sequences = any(map(lambda item: not item.is_selected, visible_sequences))
|
||||||
return has_unselected_sequences
|
return has_unselected_sequences
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
sequences = self.get_item_list(context)
|
sequences = self.get_item_list(context)
|
||||||
for sequence in get_visible_sequences(pg, sequences):
|
for sequence in get_visible_sequences(pg, sequences):
|
||||||
sequence.is_selected = True
|
sequence.is_selected = True
|
||||||
@@ -474,7 +478,7 @@ class PsaExportActionsDeselectAll(Operator):
|
|||||||
return len(item_list) > 0 and has_selected_items
|
return len(item_list) > 0 and has_selected_items
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
item_list = self.get_item_list(context)
|
item_list = self.get_item_list(context)
|
||||||
for sequence in get_visible_sequences(pg, item_list):
|
for sequence in get_visible_sequences(pg, item_list):
|
||||||
sequence.is_selected = False
|
sequence.is_selected = False
|
||||||
@@ -489,13 +493,13 @@ class PsaExportBoneGroupsSelectAll(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
item_list = pg.bone_group_list
|
item_list = pg.bone_group_list
|
||||||
has_unselected_items = any(map(lambda action: not action.is_selected, item_list))
|
has_unselected_items = any(map(lambda action: not action.is_selected, item_list))
|
||||||
return len(item_list) > 0 and has_unselected_items
|
return len(item_list) > 0 and has_unselected_items
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
for item in pg.bone_group_list:
|
for item in pg.bone_group_list:
|
||||||
item.is_selected = True
|
item.is_selected = True
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
@@ -509,13 +513,13 @@ class PsaExportBoneGroupsDeselectAll(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
item_list = pg.bone_group_list
|
item_list = pg.bone_group_list
|
||||||
has_selected_actions = any(map(lambda action: action.is_selected, item_list))
|
has_selected_actions = any(map(lambda action: action.is_selected, item_list))
|
||||||
return len(item_list) > 0 and has_selected_actions
|
return len(item_list) > 0 and has_selected_actions
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_export
|
pg = getattr(context.scene, 'psa_export')
|
||||||
for action in pg.bone_group_list:
|
for action in pg.bone_group_list:
|
||||||
action.is_selected = False
|
action.is_selected = False
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|||||||
@@ -26,38 +26,40 @@ class PsaImportOptions(object):
|
|||||||
self.action_name_prefix = ''
|
self.action_name_prefix = ''
|
||||||
|
|
||||||
|
|
||||||
|
class ImportBone(object):
|
||||||
|
def __init__(self, psa_bone: Psa.Bone):
|
||||||
|
self.psa_bone: Psa.Bone = psa_bone
|
||||||
|
self.parent: Optional[ImportBone] = None
|
||||||
|
self.armature_bone = None
|
||||||
|
self.pose_bone = None
|
||||||
|
self.orig_loc: Vector = Vector()
|
||||||
|
self.orig_quat: Quaternion = Quaternion()
|
||||||
|
self.post_quat: Quaternion = Quaternion()
|
||||||
|
self.fcurves = []
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_fcurve_data(import_bone: ImportBone, key_data: []):
|
||||||
|
# Convert world-space transforms to local-space transforms.
|
||||||
|
key_rotation = Quaternion(key_data[0:4])
|
||||||
|
key_location = Vector(key_data[4:])
|
||||||
|
q = import_bone.post_quat.copy()
|
||||||
|
q.rotate(import_bone.orig_quat)
|
||||||
|
quat = q
|
||||||
|
q = import_bone.post_quat.copy()
|
||||||
|
if import_bone.parent is None:
|
||||||
|
q.rotate(key_rotation.conjugated())
|
||||||
|
else:
|
||||||
|
q.rotate(key_rotation)
|
||||||
|
quat.rotate(q.conjugated())
|
||||||
|
loc = key_location - import_bone.orig_loc
|
||||||
|
loc.rotate(import_bone.post_quat.conjugated())
|
||||||
|
return quat.w, quat.x, quat.y, quat.z, loc.x, loc.y, loc.z
|
||||||
|
|
||||||
|
|
||||||
def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions):
|
def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions):
|
||||||
sequences = map(lambda x: psa_reader.sequences[x], options.sequence_names)
|
sequences = map(lambda x: psa_reader.sequences[x], options.sequence_names)
|
||||||
armature_data = armature_object.data
|
armature_data = armature_object.data
|
||||||
|
|
||||||
class ImportBone(object):
|
|
||||||
def __init__(self, psa_bone: Psa.Bone):
|
|
||||||
self.psa_bone: Psa.Bone = psa_bone
|
|
||||||
self.parent: Optional[ImportBone] = None
|
|
||||||
self.armature_bone = None
|
|
||||||
self.pose_bone = None
|
|
||||||
self.orig_loc: Vector = Vector()
|
|
||||||
self.orig_quat: Quaternion = Quaternion()
|
|
||||||
self.post_quat: Quaternion = Quaternion()
|
|
||||||
self.fcurves = []
|
|
||||||
|
|
||||||
def calculate_fcurve_data(import_bone: ImportBone, key_data: []):
|
|
||||||
# Convert world-space transforms to local-space transforms.
|
|
||||||
key_rotation = Quaternion(key_data[0:4])
|
|
||||||
key_location = Vector(key_data[4:])
|
|
||||||
q = import_bone.post_quat.copy()
|
|
||||||
q.rotate(import_bone.orig_quat)
|
|
||||||
quat = q
|
|
||||||
q = import_bone.post_quat.copy()
|
|
||||||
if import_bone.parent is None:
|
|
||||||
q.rotate(key_rotation.conjugated())
|
|
||||||
else:
|
|
||||||
q.rotate(key_rotation)
|
|
||||||
quat.rotate(q.conjugated())
|
|
||||||
loc = key_location - import_bone.orig_loc
|
|
||||||
loc.rotate(import_bone.post_quat.conjugated())
|
|
||||||
return quat.w, quat.x, quat.y, quat.z, loc.x, loc.y, loc.z
|
|
||||||
|
|
||||||
# Create an index mapping from bones in the PSA to bones in the target armature.
|
# Create an index mapping from bones in the PSA to bones in the target armature.
|
||||||
psa_to_armature_bone_indices = {}
|
psa_to_armature_bone_indices = {}
|
||||||
armature_bone_names = [x.name for x in armature_data.bones]
|
armature_bone_names = [x.name for x in armature_data.bones]
|
||||||
@@ -176,7 +178,8 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
|
|||||||
fcurve_frame_data = sequence_data_matrix[:, bone_index, fcurve_index]
|
fcurve_frame_data = sequence_data_matrix[:, bone_index, fcurve_index]
|
||||||
last_written_datum = 0
|
last_written_datum = 0
|
||||||
for frame_index, datum in enumerate(fcurve_frame_data):
|
for frame_index, datum in enumerate(fcurve_frame_data):
|
||||||
# If the f-curve data is not different enough to the last written frame, un-mark this data for writing.
|
# If the f-curve data is not different enough to the last written frame,
|
||||||
|
# un-mark this data for writing.
|
||||||
if frame_index > 0 and abs(datum - last_written_datum) < threshold:
|
if frame_index > 0 and abs(datum - last_written_datum) < threshold:
|
||||||
keyframe_write_matrix[frame_index, bone_index, fcurve_index] = 0
|
keyframe_write_matrix[frame_index, bone_index, fcurve_index] = 0
|
||||||
else:
|
else:
|
||||||
@@ -217,9 +220,12 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
|
|||||||
nla_track.strips.new(name=action.name, start=0, action=action)
|
nla_track.strips.new(name=action.name, start=0, action=action)
|
||||||
|
|
||||||
|
|
||||||
|
empty_set = set()
|
||||||
|
|
||||||
|
|
||||||
class PsaImportActionListItem(PropertyGroup):
|
class PsaImportActionListItem(PropertyGroup):
|
||||||
action_name: StringProperty(options=set())
|
action_name: StringProperty(options=empty_set)
|
||||||
is_selected: BoolProperty(default=False, options=set())
|
is_selected: BoolProperty(default=False, options=empty_set)
|
||||||
|
|
||||||
|
|
||||||
def load_psa_file(context):
|
def load_psa_file(context):
|
||||||
@@ -246,7 +252,7 @@ def on_psa_file_path_updated(property, context):
|
|||||||
|
|
||||||
|
|
||||||
class PsaBonePropertyGroup(PropertyGroup):
|
class PsaBonePropertyGroup(PropertyGroup):
|
||||||
bone_name: StringProperty(options=set())
|
bone_name: StringProperty(options=empty_set)
|
||||||
|
|
||||||
|
|
||||||
class PsaDataPropertyGroup(PropertyGroup):
|
class PsaDataPropertyGroup(PropertyGroup):
|
||||||
@@ -255,37 +261,37 @@ class PsaDataPropertyGroup(PropertyGroup):
|
|||||||
|
|
||||||
|
|
||||||
class PsaImportPropertyGroup(PropertyGroup):
|
class PsaImportPropertyGroup(PropertyGroup):
|
||||||
psa_file_path: StringProperty(default='', options=set(), update=on_psa_file_path_updated, name='PSA File Path')
|
psa_file_path: StringProperty(default='', options=empty_set, update=on_psa_file_path_updated, name='PSA File Path')
|
||||||
psa_error: StringProperty(default='')
|
psa_error: StringProperty(default='')
|
||||||
psa: PointerProperty(type=PsaDataPropertyGroup)
|
psa: PointerProperty(type=PsaDataPropertyGroup)
|
||||||
sequence_list: CollectionProperty(type=PsaImportActionListItem)
|
sequence_list: CollectionProperty(type=PsaImportActionListItem)
|
||||||
sequence_list_index: IntProperty(name='', default=0)
|
sequence_list_index: IntProperty(name='', default=0)
|
||||||
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes',
|
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes',
|
||||||
description='Exclude unnecessary keyframes from being written to the actions',
|
description='Exclude unnecessary keyframes from being written to the actions',
|
||||||
options=set())
|
options=empty_set)
|
||||||
should_use_fake_user: BoolProperty(default=True, name='Fake User',
|
should_use_fake_user: BoolProperty(default=True, name='Fake User',
|
||||||
description='Assign each imported action a fake user so that the data block is saved even it has no users',
|
description='Assign each imported action a fake user so that the data block is saved even it has no users',
|
||||||
options=set())
|
options=empty_set)
|
||||||
should_stash: BoolProperty(default=False, name='Stash',
|
should_stash: BoolProperty(default=False, name='Stash',
|
||||||
description='Stash each imported action as a strip on a new non-contributing NLA track',
|
description='Stash each imported action as a strip on a new non-contributing NLA track',
|
||||||
options=set())
|
options=empty_set)
|
||||||
should_use_action_name_prefix: BoolProperty(default=False, name='Prefix Action Name', options=set())
|
should_use_action_name_prefix: BoolProperty(default=False, name='Prefix Action Name', options=empty_set)
|
||||||
action_name_prefix: StringProperty(default='', name='Prefix', options=set())
|
action_name_prefix: StringProperty(default='', name='Prefix', options=empty_set)
|
||||||
should_overwrite: BoolProperty(default=False, name='Reuse Existing Actions', options=set(),
|
should_overwrite: BoolProperty(default=False, name='Reuse Existing Actions', options=empty_set,
|
||||||
description='If an action with a matching name already exists, the existing action will have it\'s data overwritten instead of a new action being created')
|
description='If an action with a matching name already exists, the existing action will have it\'s data overwritten instead of a new action being created')
|
||||||
should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=set())
|
should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=empty_set)
|
||||||
should_write_metadata: BoolProperty(default=True, name='Metadata', options=set(),
|
should_write_metadata: BoolProperty(default=True, name='Metadata', options=empty_set,
|
||||||
description='Additional data will be written to the custom properties of the Action (e.g., frame rate)')
|
description='Additional data will be written to the custom properties of the Action (e.g., frame rate)')
|
||||||
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
||||||
sequence_filter_is_selected: BoolProperty(default=False, options=set(), name='Only Show Selected',
|
sequence_filter_is_selected: BoolProperty(default=False, options=empty_set, name='Only Show Selected',
|
||||||
description='Only show selected sequences')
|
description='Only show selected sequences')
|
||||||
sequence_use_filter_invert: BoolProperty(default=False, options=set())
|
sequence_use_filter_invert: BoolProperty(default=False, options=empty_set)
|
||||||
sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression',
|
sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression',
|
||||||
description='Filter using regular expressions', options=set())
|
description='Filter using regular expressions', options=empty_set)
|
||||||
select_text: PointerProperty(type=bpy.types.Text)
|
select_text: PointerProperty(type=bpy.types.Text)
|
||||||
|
|
||||||
|
|
||||||
def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[int]:
|
def filter_sequences(pg: PsaImportPropertyGroup, sequences) -> List[int]:
|
||||||
bitflag_filter_item = 1 << 30
|
bitflag_filter_item = 1 << 30
|
||||||
flt_flags = [bitflag_filter_item] * len(sequences)
|
flt_flags = [bitflag_filter_item] * len(sequences)
|
||||||
|
|
||||||
@@ -319,8 +325,7 @@ def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_c
|
|||||||
return flt_flags
|
return flt_flags
|
||||||
|
|
||||||
|
|
||||||
def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[
|
def get_visible_sequences(pg: PsaImportPropertyGroup, sequences) -> List[PsaImportActionListItem]:
|
||||||
PsaImportActionListItem]:
|
|
||||||
bitflag_filter_item = 1 << 30
|
bitflag_filter_item = 1 << 30
|
||||||
visible_sequences = []
|
visible_sequences = []
|
||||||
for i, flag in enumerate(filter_sequences(pg, sequences)):
|
for i, flag in enumerate(filter_sequences(pg, sequences)):
|
||||||
@@ -330,26 +335,25 @@ def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_p
|
|||||||
|
|
||||||
|
|
||||||
class PSA_UL_SequenceList(UIList):
|
class PSA_UL_SequenceList(UIList):
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index, flt_flag):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
split = row.split(align=True, factor=0.75)
|
split = row.split(align=True, factor=0.75)
|
||||||
column = split.row(align=True)
|
column = split.row(align=True)
|
||||||
column.alignment = 'LEFT'
|
column.alignment = 'LEFT'
|
||||||
column.prop(item, 'is_selected', icon_only=True)
|
column.prop(item, 'is_selected', icon_only=True)
|
||||||
column.label(text=item.action_name)
|
column.label(text=getattr(item, 'action_name'))
|
||||||
|
|
||||||
def draw_filter(self, context, layout):
|
def draw_filter(self, context, layout):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
subrow = row.row(align=True)
|
sub_row = row.row(align=True)
|
||||||
subrow.prop(pg, 'sequence_filter_name', text="")
|
sub_row.prop(pg, 'sequence_filter_name', text="")
|
||||||
subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
|
sub_row.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
|
||||||
subrow.prop(pg, 'sequence_use_filter_regex', text="", icon='SORTBYEXT')
|
sub_row.prop(pg, 'sequence_use_filter_regex', text="", icon='SORTBYEXT')
|
||||||
subrow.prop(pg, 'sequence_filter_is_selected', text="", icon='CHECKBOX_HLT')
|
sub_row.prop(pg, 'sequence_filter_is_selected', text="", icon='CHECKBOX_HLT')
|
||||||
|
|
||||||
def filter_items(self, context, data, property):
|
def filter_items(self, context, data, property):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
sequences = getattr(data, property)
|
sequences = getattr(data, property)
|
||||||
flt_flags = filter_sequences(pg, sequences)
|
flt_flags = filter_sequences(pg, sequences)
|
||||||
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'action_name')
|
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'action_name')
|
||||||
@@ -372,7 +376,7 @@ class PsaImportSequencesFromText(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
return len(pg.sequence_list) > 0
|
return len(pg.sequence_list) > 0
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@@ -380,12 +384,12 @@ class PsaImportSequencesFromText(Operator):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
layout.label(icon='INFO', text='Each sequence name should be on a new line.')
|
layout.label(icon='INFO', text='Each sequence name should be on a new line.')
|
||||||
layout.prop(pg, 'select_text', text='')
|
layout.prop(pg, 'select_text', text='')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
if pg.select_text is None:
|
if pg.select_text is None:
|
||||||
self.report({'ERROR_INVALID_CONTEXT'}, 'No text block selected')
|
self.report({'ERROR_INVALID_CONTEXT'}, 'No text block selected')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
@@ -408,13 +412,13 @@ class PsaImportSequencesSelectAll(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
||||||
has_unselected_actions = any(map(lambda action: not action.is_selected, visible_sequences))
|
has_unselected_actions = any(map(lambda action: not action.is_selected, visible_sequences))
|
||||||
return len(visible_sequences) > 0 and has_unselected_actions
|
return len(visible_sequences) > 0 and has_unselected_actions
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
||||||
for sequence in visible_sequences:
|
for sequence in visible_sequences:
|
||||||
sequence.is_selected = True
|
sequence.is_selected = True
|
||||||
@@ -429,13 +433,13 @@ class PsaImportSequencesDeselectAll(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
||||||
has_selected_sequences = any(map(lambda sequence: sequence.is_selected, visible_sequences))
|
has_selected_sequences = any(map(lambda sequence: sequence.is_selected, visible_sequences))
|
||||||
return len(visible_sequences) > 0 and has_selected_sequences
|
return len(visible_sequences) > 0 and has_selected_sequences
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
visible_sequences = get_visible_sequences(pg, pg.sequence_list)
|
||||||
for sequence in visible_sequences:
|
for sequence in visible_sequences:
|
||||||
sequence.is_selected = False
|
sequence.is_selected = False
|
||||||
@@ -451,7 +455,7 @@ class PSA_PT_ImportPanel_Advanced(Panel):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
|
|
||||||
col = layout.column(heading="Options")
|
col = layout.column(heading="Options")
|
||||||
col.use_property_split = True
|
col.use_property_split = True
|
||||||
@@ -476,11 +480,11 @@ class PSA_PT_ImportPanel(Panel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.object.type == 'ARMATURE'
|
return context.view_layer.objects.active.type == 'ARMATURE'
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
|
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.operator(PsaImportSelectFile.bl_idname, text='', icon='FILEBROWSER')
|
row.operator(PsaImportSelectFile.bl_idname, text='', icon='FILEBROWSER')
|
||||||
@@ -552,7 +556,7 @@ class PsaImportSelectFile(Operator):
|
|||||||
filter_glob: bpy.props.StringProperty(default="*.psa", options={'HIDDEN'})
|
filter_glob: bpy.props.StringProperty(default="*.psa", options={'HIDDEN'})
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
context.scene.psa_import.psa_file_path = self.filepath
|
getattr(context.scene, 'psa_import').psa_file_path = self.filepath
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@@ -568,14 +572,14 @@ class PsaImportOperator(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
active_object = context.view_layer.objects.active
|
active_object = context.view_layer.objects.active
|
||||||
if active_object is None or active_object.type != 'ARMATURE':
|
if active_object is None or active_object.type != 'ARMATURE':
|
||||||
return False
|
return False
|
||||||
return any(map(lambda x: x.is_selected, pg.sequence_list))
|
return any(map(lambda x: x.is_selected, pg.sequence_list))
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
psa_reader = PsaReader(pg.psa_file_path)
|
psa_reader = PsaReader(pg.psa_file_path)
|
||||||
sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected]
|
sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected]
|
||||||
|
|
||||||
@@ -613,7 +617,7 @@ class PsaImportFileSelectOperator(Operator, ImportHelper):
|
|||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_import
|
pg = getattr(context.scene, 'psa_import')
|
||||||
pg.psa_file_path = self.filepath
|
pg.psa_file_path = self.filepath
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ from .data import *
|
|||||||
class PsaReader(object):
|
class PsaReader(object):
|
||||||
"""
|
"""
|
||||||
This class reads the sequences and bone information immediately upon instantiation and holds onto a file handle.
|
This class reads the sequences and bone information immediately upon instantiation and holds onto a file handle.
|
||||||
The keyframe data is not read into memory upon instantiation due to it's potentially very large size.
|
The keyframe data is not read into memory upon instantiation due to its potentially very large size.
|
||||||
To read the key data for a particular sequence, call `read_sequence_keys`.
|
To read the key data for a particular sequence, call :read_sequence_keys.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
@@ -38,7 +38,8 @@ class PsaReader(object):
|
|||||||
return matrix
|
return matrix
|
||||||
|
|
||||||
def read_sequence_keys(self, sequence_name: str) -> List[Psa.Key]:
|
def read_sequence_keys(self, sequence_name: str) -> List[Psa.Key]:
|
||||||
""" Reads and returns the key data for a sequence.
|
"""
|
||||||
|
Reads and returns the key data for a sequence.
|
||||||
|
|
||||||
:param sequence_name: The name of the sequence.
|
:param sequence_name: The name of the sequence.
|
||||||
:return: A list of Psa.Keys.
|
:return: A list of Psa.Keys.
|
||||||
@@ -60,7 +61,7 @@ class PsaReader(object):
|
|||||||
return keys
|
return keys
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _read_types(fp, data_class: ctypes.Structure, section: Section, data):
|
def _read_types(fp, data_class, section: Section, data):
|
||||||
buffer_length = section.data_size * section.data_count
|
buffer_length = section.data_size * section.data_count
|
||||||
buffer = fp.read(buffer_length)
|
buffer = fp.read(buffer_length)
|
||||||
offset = 0
|
offset = 0
|
||||||
@@ -86,7 +87,7 @@ class PsaReader(object):
|
|||||||
# Skip keys on this pass. We will keep this file open and read from it as needed.
|
# Skip keys on this pass. We will keep this file open and read from it as needed.
|
||||||
self.keys_data_offset = fp.tell()
|
self.keys_data_offset = fp.tell()
|
||||||
fp.seek(section.data_size * section.data_count, 1)
|
fp.seek(section.data_size * section.data_count, 1)
|
||||||
elif section.name in [b'SCALEKEYS']:
|
elif section.name == b'SCALEKEYS':
|
||||||
fp.seek(section.data_size * section.data_count, 1)
|
fp.seek(section.data_size * section.data_count, 1)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f'Unrecognized section "{section.name}"')
|
raise RuntimeError(f'Unrecognized section "{section.name}"')
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty, PointerProperty
|
from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty
|
||||||
from bpy.types import Operator, PropertyGroup, UIList, Material
|
from bpy.types import Operator, PropertyGroup, UIList
|
||||||
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
|
||||||
@@ -70,7 +70,7 @@ def is_bone_filter_mode_item_available(context, identifier):
|
|||||||
class PSK_UL_MaterialList(UIList):
|
class PSK_UL_MaterialList(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.label(text=str(item.material_name), icon='MATERIAL')
|
row.label(text=str(getattr(item, 'material_name')), icon='MATERIAL')
|
||||||
|
|
||||||
|
|
||||||
class MaterialListItem(PropertyGroup):
|
class MaterialListItem(PropertyGroup):
|
||||||
@@ -108,11 +108,11 @@ class PskMaterialListItemMoveUp(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psk_export
|
pg = getattr(context.scene, 'psk_export')
|
||||||
return pg.material_list_index > 0
|
return pg.material_list_index > 0
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psk_export
|
pg = getattr(context.scene, 'psk_export')
|
||||||
pg.material_list.move(pg.material_list_index, pg.material_list_index - 1)
|
pg.material_list.move(pg.material_list_index, pg.material_list_index - 1)
|
||||||
pg.material_list_index -= 1
|
pg.material_list_index -= 1
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -126,11 +126,11 @@ class PskMaterialListItemMoveDown(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psk_export
|
pg = getattr(context.scene, 'psk_export')
|
||||||
return pg.material_list_index < len(pg.material_list) - 1
|
return pg.material_list_index < len(pg.material_list) - 1
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psk_export
|
pg = getattr(context.scene, 'psk_export')
|
||||||
pg.material_list.move(pg.material_list_index, pg.material_list_index + 1)
|
pg.material_list.move(pg.material_list_index, pg.material_list_index + 1)
|
||||||
pg.material_list_index += 1
|
pg.material_list_index += 1
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -157,7 +157,7 @@ class PskExportOperator(Operator, ExportHelper):
|
|||||||
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
pg = context.scene.psk_export
|
pg = getattr(context.scene, 'psk_export')
|
||||||
|
|
||||||
# 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)
|
||||||
@@ -178,8 +178,7 @@ class PskExportOperator(Operator, ExportHelper):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
scene = context.scene
|
pg = getattr(context.scene, 'psk_export')
|
||||||
pg = scene.psk_export
|
|
||||||
|
|
||||||
layout.prop(pg, 'use_raw_mesh_data')
|
layout.prop(pg, 'use_raw_mesh_data')
|
||||||
|
|
||||||
@@ -220,6 +219,7 @@ class PskExportOperator(Operator, ExportHelper):
|
|||||||
try:
|
try:
|
||||||
psk = build_psk(context, options)
|
psk = build_psk(context, options)
|
||||||
export_psk(psk, self.filepath)
|
export_psk(psk, self.filepath)
|
||||||
|
self.report({'INFO'}, f'PSK export successful')
|
||||||
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'}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from math import inf
|
from math import inf
|
||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
import bmesh
|
import bmesh
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty
|
from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty
|
||||||
from bpy.types import Operator, PropertyGroup
|
from bpy.types import Operator, PropertyGroup, VertexGroup
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
from mathutils import Quaternion, Vector, Matrix
|
from mathutils import Quaternion, Vector, Matrix
|
||||||
|
|
||||||
@@ -28,6 +28,24 @@ class PskImportOptions(object):
|
|||||||
self.bone_length = 1.0
|
self.bone_length = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
class ImportBone(object):
|
||||||
|
"""
|
||||||
|
Intermediate bone type for the purpose of construction.
|
||||||
|
"""
|
||||||
|
def __init__(self, index: int, psk_bone: Psk.Bone):
|
||||||
|
self.index: int = index
|
||||||
|
self.psk_bone: Psk.Bone = psk_bone
|
||||||
|
self.parent: Optional[ImportBone] = None
|
||||||
|
self.local_rotation: Quaternion = Quaternion()
|
||||||
|
self.local_translation: Vector = Vector()
|
||||||
|
self.world_rotation_matrix: Matrix = Matrix()
|
||||||
|
self.world_matrix: Matrix = Matrix()
|
||||||
|
self.vertex_group = None
|
||||||
|
self.orig_quat: Quaternion = Quaternion()
|
||||||
|
self.orig_loc: Vector = Vector()
|
||||||
|
self.post_quat: Quaternion = Quaternion()
|
||||||
|
|
||||||
|
|
||||||
def import_psk(psk: Psk, context, options: PskImportOptions):
|
def import_psk(psk: Psk, context, options: PskImportOptions):
|
||||||
armature_object = None
|
armature_object = None
|
||||||
|
|
||||||
@@ -49,21 +67,6 @@ def import_psk(psk: Psk, context, options: PskImportOptions):
|
|||||||
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
# Intermediate bone type for the purpose of construction.
|
|
||||||
class ImportBone(object):
|
|
||||||
def __init__(self, index: int, psk_bone: Psk.Bone):
|
|
||||||
self.index: int = index
|
|
||||||
self.psk_bone: Psk.Bone = psk_bone
|
|
||||||
self.parent: Optional[ImportBone] = None
|
|
||||||
self.local_rotation: Quaternion = Quaternion()
|
|
||||||
self.local_translation: Vector = Vector()
|
|
||||||
self.world_rotation_matrix: Matrix = Matrix()
|
|
||||||
self.world_matrix: Matrix = Matrix()
|
|
||||||
self.vertex_group = None
|
|
||||||
self.orig_quat: Quaternion = Quaternion()
|
|
||||||
self.orig_loc: Vector = Vector()
|
|
||||||
self.post_quat: Quaternion = Quaternion()
|
|
||||||
|
|
||||||
import_bones = []
|
import_bones = []
|
||||||
|
|
||||||
for bone_index, psk_bone in enumerate(psk.bones):
|
for bone_index, psk_bone in enumerate(psk.bones):
|
||||||
@@ -213,7 +216,7 @@ def import_psk(psk: Psk, context, options: PskImportOptions):
|
|||||||
|
|
||||||
# Get a list of all bones that have weights associated with them.
|
# Get a list of all bones that have weights associated with them.
|
||||||
vertex_group_bone_indices = set(map(lambda weight: weight.bone_index, psk.weights))
|
vertex_group_bone_indices = set(map(lambda weight: weight.bone_index, psk.weights))
|
||||||
vertex_groups = [None] * len(psk.bones)
|
vertex_groups: List[Optional[VertexGroup]] = [None] * len(psk.bones)
|
||||||
for bone_index, psk_bone in map(lambda x: (x, psk.bones[x]), vertex_group_bone_indices):
|
for bone_index, psk_bone in map(lambda x: (x, psk.bones[x]), vertex_group_bone_indices):
|
||||||
vertex_groups[bone_index] = mesh_object.vertex_groups.new(name=psk_bone.name.decode('windows-1252'))
|
vertex_groups[bone_index] = mesh_object.vertex_groups.new(name=psk_bone.name.decode('windows-1252'))
|
||||||
|
|
||||||
@@ -234,16 +237,19 @@ def import_psk(psk: Psk, context, options: PskImportOptions):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
empty_set = set()
|
||||||
|
|
||||||
|
|
||||||
class PskImportPropertyGroup(PropertyGroup):
|
class PskImportPropertyGroup(PropertyGroup):
|
||||||
should_import_vertex_colors: BoolProperty(
|
should_import_vertex_colors: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
options=set(),
|
options=empty_set,
|
||||||
name='Vertex Colors',
|
name='Vertex Colors',
|
||||||
description='Import vertex colors from PSKX files, if available'
|
description='Import vertex colors from PSKX files, if available'
|
||||||
)
|
)
|
||||||
vertex_color_space: EnumProperty(
|
vertex_color_space: EnumProperty(
|
||||||
name='Vertex Color Space',
|
name='Vertex Color Space',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='The source vertex color space',
|
description='The source vertex color space',
|
||||||
default='SRGBA',
|
default='SRGBA',
|
||||||
items=(
|
items=(
|
||||||
@@ -254,25 +260,25 @@ class PskImportPropertyGroup(PropertyGroup):
|
|||||||
should_import_vertex_normals: BoolProperty(
|
should_import_vertex_normals: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
name='Vertex Normals',
|
name='Vertex Normals',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Import vertex normals from PSKX files, if available'
|
description='Import vertex normals from PSKX files, if available'
|
||||||
)
|
)
|
||||||
should_import_extra_uvs: BoolProperty(
|
should_import_extra_uvs: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
name='Extra UVs',
|
name='Extra UVs',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Import extra UV maps from PSKX files, if available'
|
description='Import extra UV maps from PSKX files, if available'
|
||||||
)
|
)
|
||||||
should_import_mesh: BoolProperty(
|
should_import_mesh: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
name='Import Mesh',
|
name='Import Mesh',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Import mesh'
|
description='Import mesh'
|
||||||
)
|
)
|
||||||
should_import_skeleton: BoolProperty(
|
should_import_skeleton: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
name='Import Skeleton',
|
name='Import Skeleton',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Import skeleton'
|
description='Import skeleton'
|
||||||
)
|
)
|
||||||
bone_length: FloatProperty(
|
bone_length: FloatProperty(
|
||||||
@@ -281,7 +287,7 @@ class PskImportPropertyGroup(PropertyGroup):
|
|||||||
step=100,
|
step=100,
|
||||||
soft_min=1.0,
|
soft_min=1.0,
|
||||||
name='Bone Length',
|
name='Bone Length',
|
||||||
options=set(),
|
options=empty_set,
|
||||||
description='Length of the bones'
|
description='Length of the bones'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -300,7 +306,7 @@ class PskImportOperator(Operator, ImportHelper):
|
|||||||
default='')
|
default='')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psk_import
|
pg = getattr(context.scene, 'psk_import')
|
||||||
|
|
||||||
psk = read_psk(self.filepath)
|
psk = read_psk(self.filepath)
|
||||||
|
|
||||||
@@ -319,7 +325,7 @@ class PskImportOperator(Operator, ImportHelper):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
pg = context.scene.psk_import
|
pg = getattr(context.scene, 'psk_import')
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.prop(pg, 'should_import_mesh')
|
layout.prop(pg, 'should_import_mesh')
|
||||||
row = layout.column()
|
row = layout.column()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ctypes
|
|||||||
from .data import *
|
from .data import *
|
||||||
|
|
||||||
|
|
||||||
def _read_types(fp, data_class: ctypes.Structure, section: Section, data):
|
def _read_types(fp, data_class, section: Section, data):
|
||||||
buffer_length = section.data_size * section.data_count
|
buffer_length = section.data_size * section.data_count
|
||||||
buffer = fp.read(buffer_length)
|
buffer = fp.read(buffer_length)
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
from bpy.props import StringProperty, IntProperty, BoolProperty
|
from bpy.props import StringProperty, IntProperty, BoolProperty
|
||||||
from bpy.types import PropertyGroup, UIList
|
from bpy.types import PropertyGroup, UIList, UILayout, Context, AnyType
|
||||||
|
|
||||||
|
|
||||||
class PSX_UL_BoneGroupList(UIList):
|
class PSX_UL_BoneGroupList(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
|
||||||
|
def draw_item(self, context: Context, layout: UILayout, data: AnyType, item: AnyType, icon: int,
|
||||||
|
active_data: AnyType, active_property: str, index: int = 0, flt_flag: int = 0):
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(item, 'is_selected', text=item.name)
|
row.prop(item, 'is_selected', text=getattr(item, 'name'))
|
||||||
row.label(text=str(item.count), icon='BONE_DATA')
|
row.label(text=str(getattr(item, 'count')), icon='BONE_DATA')
|
||||||
|
|
||||||
|
|
||||||
class BoneGroupListItem(PropertyGroup):
|
class BoneGroupListItem(PropertyGroup):
|
||||||
|
|||||||
Reference in New Issue
Block a user