* Fixed a bug where the action name prefix could be applied even if the checkbox was deselected
* Fixed a typo in the PSA reader * Added the ability to define the bone length of imported PSK armatures * The PSK import options (extra UVs, vertex colors etc.) are now actually respected if turned off * Ran automated formatting on all the code to quell the PEP8 gods * Incremented version to 3.0.0
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
from typing import Dict, Iterable
|
||||
|
||||
from bpy.types import Action
|
||||
|
||||
from .data import *
|
||||
from ..helpers import *
|
||||
from typing import Dict, Iterable
|
||||
from bpy.types import Action
|
||||
|
||||
|
||||
class PsaBuilderOptions(object):
|
||||
@@ -37,13 +39,13 @@ class PsaBuilder(object):
|
||||
return options.fps_custom
|
||||
elif options.fps_source == 'ACTION_METADATA':
|
||||
# Get the minimum value of action metadata FPS values.
|
||||
psa_fps_list = []
|
||||
for action in filter(lambda x: 'psa_fps' in x, actions):
|
||||
psa_fps = action['psa_fps']
|
||||
if type(psa_fps) == int or type(psa_fps) == float:
|
||||
psa_fps_list.append(psa_fps)
|
||||
if len(psa_fps_list) > 0:
|
||||
return min(psa_fps_list)
|
||||
fps_list = []
|
||||
for action in filter(lambda x: 'psa_sequence_fps' in x, actions):
|
||||
fps = action['psa_sequence_fps']
|
||||
if type(fps) == int or type(fps) == float:
|
||||
fps_list.append(fps)
|
||||
if len(fps_list) > 0:
|
||||
return min(fps_list)
|
||||
else:
|
||||
# No valid action metadata to use, fallback to scene FPS
|
||||
return context.scene.render.fps
|
||||
@@ -166,7 +168,8 @@ class PsaBuilder(object):
|
||||
export_sequence.nla_state.action = None
|
||||
export_sequence.nla_state.frame_min = frame_min
|
||||
export_sequence.nla_state.frame_max = frame_max
|
||||
nla_strips_actions = set(map(lambda x: x.action, get_nla_strips_in_timeframe(active_object, frame_min, frame_max)))
|
||||
nla_strips_actions = set(
|
||||
map(lambda x: x.action, get_nla_strips_in_timeframe(active_object, frame_min, frame_max)))
|
||||
export_sequence.fps = self.get_sequence_fps(context, options, nla_strips_actions)
|
||||
export_sequences.append(export_sequence)
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import typing
|
||||
from typing import List
|
||||
from collections import OrderedDict
|
||||
from typing import List
|
||||
|
||||
from ..data import *
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import bpy
|
||||
from bpy.types import Operator, PropertyGroup, Action, UIList, BoneGroup, Panel, TimelineMarker
|
||||
from bpy.props import CollectionProperty, IntProperty, FloatProperty, PointerProperty, StringProperty, BoolProperty, EnumProperty
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
from typing import Type
|
||||
from .builder import PsaBuilder, PsaBuilderOptions
|
||||
from .data import *
|
||||
from ..types import BoneGroupListItem
|
||||
from ..helpers import *
|
||||
from collections import Counter
|
||||
import fnmatch
|
||||
import re
|
||||
import sys
|
||||
import fnmatch
|
||||
from collections import Counter
|
||||
from typing import Type
|
||||
|
||||
import bpy
|
||||
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, FloatProperty, IntProperty, PointerProperty, \
|
||||
StringProperty
|
||||
from bpy.types import Action, Operator, PropertyGroup, UIList
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
|
||||
from .builder import PsaBuilder, PsaBuilderOptions
|
||||
from .data import *
|
||||
from ..helpers import *
|
||||
from ..types import BoneGroupListItem
|
||||
|
||||
|
||||
class PsaExporter(object):
|
||||
@@ -57,7 +60,7 @@ def update_action_names(context):
|
||||
item.action_name = get_psa_sequence_name(action, pg.should_use_original_sequence_names)
|
||||
|
||||
|
||||
def should_use_original_sequence_names_updated(property, context):
|
||||
def should_use_original_sequence_names_updated(_, context):
|
||||
update_action_names(context)
|
||||
|
||||
|
||||
@@ -68,7 +71,8 @@ class PsaExportPropertyGroup(PropertyGroup):
|
||||
description='',
|
||||
items=(
|
||||
('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
|
||||
('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences will be exported using timeline markers', 'MARKER_HLT', 1),
|
||||
('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences will be exported using timeline markers', 'MARKER_HLT',
|
||||
1),
|
||||
)
|
||||
)
|
||||
fps_source: EnumProperty(
|
||||
@@ -77,11 +81,14 @@ class PsaExportPropertyGroup(PropertyGroup):
|
||||
description='',
|
||||
items=(
|
||||
('SCENE', 'Scene', '', 'SCENE_DATA', 0),
|
||||
('ACTION_METADATA', 'Action Metadata', 'The frame rate will be determined by action\'s "psa_fps" custom property, if it exists. If the Sequence Source is Timeline Markers, the lowest value of all contributing actions will be used. If no metadata is available, the scene\'s frame rate will be used.', 'PROPERTIES', 1),
|
||||
('ACTION_METADATA', 'Action Metadata',
|
||||
'The frame rate will be determined by action\'s "psa_sequence_fps" custom property, if it exists. If the Sequence Source is Timeline Markers, the lowest value of all contributing actions will be used. If no metadata is available, the scene\'s frame rate will be used.',
|
||||
'PROPERTIES', 1),
|
||||
('CUSTOM', 'Custom', '', 2)
|
||||
)
|
||||
)
|
||||
fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=set(), step=100, soft_max=60.0)
|
||||
fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=set(), step=100,
|
||||
soft_max=60.0)
|
||||
action_list: CollectionProperty(type=PsaExportActionListItem)
|
||||
action_list_index: IntProperty(default=0)
|
||||
marker_list: CollectionProperty(type=PsaExportTimelineMarkerListItem)
|
||||
@@ -117,7 +124,8 @@ class PsaExportPropertyGroup(PropertyGroup):
|
||||
sequence_name_suffix: StringProperty(name='Suffix', options=set())
|
||||
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
||||
sequence_use_filter_invert: BoolProperty(default=False, options=set())
|
||||
sequence_filter_asset: BoolProperty(default=False, name='Show assets', description='Show actions that belong to an asset library', options=set())
|
||||
sequence_filter_asset: BoolProperty(default=False, name='Show assets',
|
||||
description='Show actions that belong to an asset library', options=set())
|
||||
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set())
|
||||
|
||||
|
||||
@@ -178,7 +186,8 @@ class PsaExportOperator(Operator, ExportHelper):
|
||||
|
||||
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||
rows = max(3, min(len(pg.marker_list), 10))
|
||||
layout.template_list('PSA_UL_ExportTimelineMarkerList', '', pg, 'marker_list', pg, 'marker_list_index', rows=rows)
|
||||
layout.template_list('PSA_UL_ExportTimelineMarkerList', '', pg, 'marker_list', pg, 'marker_list_index',
|
||||
rows=rows)
|
||||
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
@@ -208,7 +217,8 @@ class PsaExportOperator(Operator, ExportHelper):
|
||||
row.operator(PsaExportBoneGroupsSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT')
|
||||
row.operator(PsaExportBoneGroupsDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT')
|
||||
rows = max(3, min(len(pg.bone_group_list), 10))
|
||||
layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows)
|
||||
layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index',
|
||||
rows=rows)
|
||||
|
||||
def should_action_be_selected_by_default(self, action):
|
||||
return action is not None and action.asset_data is None
|
||||
@@ -336,7 +346,8 @@ def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_c
|
||||
return flt_flags
|
||||
|
||||
|
||||
def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaExportActionListItem]:
|
||||
def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[
|
||||
PsaExportActionListItem]:
|
||||
visible_sequences = []
|
||||
for i, flag in enumerate(filter_sequences(pg, sequences)):
|
||||
if bool(flag & (1 << 30)):
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import bpy
|
||||
import os
|
||||
import numpy as np
|
||||
import re
|
||||
import fnmatch
|
||||
from mathutils import Vector, Quaternion, Matrix
|
||||
from .data import Psa
|
||||
from typing import List, AnyStr, Optional
|
||||
from bpy.types import Operator, Action, UIList, PropertyGroup, Panel, Armature, FileSelectParams
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
import os
|
||||
import re
|
||||
from typing import List, Optional
|
||||
|
||||
import bpy
|
||||
import numpy as np
|
||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty
|
||||
from bpy.types import Operator, UIList, PropertyGroup, Panel
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from mathutils import Vector, Quaternion
|
||||
|
||||
from .data import Psa
|
||||
from .reader import PsaReader
|
||||
|
||||
|
||||
@@ -18,7 +20,6 @@ class PsaImportOptions(object):
|
||||
self.should_use_fake_user = False
|
||||
self.should_stash = False
|
||||
self.sequence_names = []
|
||||
self.should_use_action_name_prefix = False
|
||||
self.should_overwrite = False
|
||||
self.should_write_keyframes = True
|
||||
self.should_write_metadata = True
|
||||
@@ -76,7 +77,8 @@ class PsaImporter(object):
|
||||
# Report if there are missing bones in the target armature.
|
||||
missing_bone_names = set(psa_bone_names).difference(set(armature_bone_names))
|
||||
if len(missing_bone_names) > 0:
|
||||
print(f'The armature object \'{armature_object.name}\' is missing the following bones that exist in the PSA:')
|
||||
print(
|
||||
f'The armature object \'{armature_object.name}\' is missing the following bones that exist in the PSA:')
|
||||
print(list(sorted(missing_bone_names)))
|
||||
del armature_bone_names
|
||||
|
||||
@@ -192,14 +194,16 @@ class PsaImporter(object):
|
||||
if bone_has_writeable_keyframes:
|
||||
# This bone has writeable keyframes for this frame.
|
||||
key_data = sequence_data_matrix[frame_index, bone_index]
|
||||
for fcurve, should_write, datum in zip(import_bone.fcurves, keyframe_write_matrix[frame_index, bone_index], key_data):
|
||||
for fcurve, should_write, datum in zip(import_bone.fcurves,
|
||||
keyframe_write_matrix[frame_index, bone_index],
|
||||
key_data):
|
||||
if should_write:
|
||||
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
|
||||
|
||||
# Write
|
||||
if options.should_write_metadata:
|
||||
action['psa_sequence_name'] = sequence_name
|
||||
action['psa_fps'] = sequence.fps
|
||||
action['psa_sequence_fps'] = sequence.fps
|
||||
|
||||
action.use_fake_user = options.should_use_fake_user
|
||||
|
||||
@@ -259,18 +263,28 @@ class PsaImportPropertyGroup(PropertyGroup):
|
||||
psa: PointerProperty(type=PsaDataPropertyGroup)
|
||||
sequence_list: CollectionProperty(type=PsaImportActionListItem)
|
||||
sequence_list_index: IntProperty(name='', default=0)
|
||||
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', description='Exclude unnecessary keyframes from being written to the actions.', options=set())
|
||||
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.', options=set())
|
||||
should_stash: BoolProperty(default=False, name='Stash', description='Stash each imported action as a strip on a new non-contributing NLA track', options=set())
|
||||
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes',
|
||||
description='Exclude unnecessary keyframes from being written to the actions',
|
||||
options=set())
|
||||
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',
|
||||
options=set())
|
||||
should_stash: BoolProperty(default=False, name='Stash',
|
||||
description='Stash each imported action as a strip on a new non-contributing NLA track',
|
||||
options=set())
|
||||
should_use_action_name_prefix: BoolProperty(default=False, name='Prefix Action Name', options=set())
|
||||
should_overwrite: BoolProperty(default=False, name='Reuse Existing Datablocks', options=set())
|
||||
should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=set())
|
||||
should_write_metadata: BoolProperty(default=True, name='Metadata', options=set(), description='Additional data will be written to the custom properties of the Action (e.g., frame rate)')
|
||||
action_name_prefix: StringProperty(default='', name='Prefix', options=set())
|
||||
should_overwrite: BoolProperty(default=False, name='Reuse Existing Actions', options=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')
|
||||
should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=set())
|
||||
should_write_metadata: BoolProperty(default=True, name='Metadata', options=set(),
|
||||
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_is_selected: BoolProperty(default=False, options=set(), name='Only Show Selected', description='Only show selected sequences')
|
||||
sequence_filter_is_selected: BoolProperty(default=False, options=set(), name='Only Show Selected',
|
||||
description='Only show selected sequences')
|
||||
sequence_use_filter_invert: BoolProperty(default=False, options=set())
|
||||
sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression', description='Filter using regular expressions', options=set())
|
||||
sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression',
|
||||
description='Filter using regular expressions', options=set())
|
||||
select_text: PointerProperty(type=bpy.types.Text)
|
||||
|
||||
|
||||
@@ -290,7 +304,7 @@ def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_c
|
||||
except re.error:
|
||||
pass
|
||||
else:
|
||||
# User regular matching
|
||||
# User regular text matching.
|
||||
for i, sequence in enumerate(sequences):
|
||||
if not fnmatch.fnmatch(sequence.action_name, f'*{pg.sequence_filter_name}*'):
|
||||
flt_flags[i] &= ~bitflag_filter_item
|
||||
@@ -308,7 +322,8 @@ def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_c
|
||||
return flt_flags
|
||||
|
||||
|
||||
def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaImportActionListItem]:
|
||||
def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[
|
||||
PsaImportActionListItem]:
|
||||
bitflag_filter_item = 1 << 30
|
||||
visible_sequences = []
|
||||
for i, flag in enumerate(filter_sequences(pg, sequences)):
|
||||
@@ -451,21 +466,6 @@ class PSA_PT_ImportPanel_Advanced(Panel):
|
||||
col.prop(pg, 'action_name_prefix')
|
||||
|
||||
|
||||
class PSA_PT_ImportPanel_PsaData(Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_label = 'PSA Info'
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = 'PSA_PT_ImportPanel'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pg = context.scene.psa_import.psa
|
||||
|
||||
layout.label(text=f'{len(pg.bones)} Bones', icon='BONE_DATA')
|
||||
layout.label(text=f'{pg.sequence_count} Sequences', icon='SEQUENCE')
|
||||
|
||||
|
||||
class PSA_PT_ImportPanel(Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
@@ -584,7 +584,7 @@ class PsaImportOperator(Operator):
|
||||
options.should_clean_keys = pg.should_clean_keys
|
||||
options.should_use_fake_user = pg.should_use_fake_user
|
||||
options.should_stash = pg.should_stash
|
||||
options.action_name_prefix = pg.action_name_prefix
|
||||
options.action_name_prefix = pg.action_name_prefix if pg.should_use_action_name_prefix else ''
|
||||
options.should_overwrite = pg.should_overwrite
|
||||
options.should_write_metadata = pg.should_write_metadata
|
||||
options.should_write_keyframes = pg.should_write_keyframes
|
||||
@@ -632,7 +632,6 @@ classes = (
|
||||
PsaImportFileReload,
|
||||
PSA_PT_ImportPanel,
|
||||
PSA_PT_ImportPanel_Advanced,
|
||||
PSA_PT_ImportPanel_PsaData,
|
||||
PsaImportOperator,
|
||||
PsaImportFileSelectOperator,
|
||||
PsaImportSelectFile,
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from .data import *
|
||||
import ctypes
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .data import *
|
||||
|
||||
|
||||
class PsaReader(object):
|
||||
"""
|
||||
This class will read the sequences and bone information immediately upon instantiation and hold onto a file handle.
|
||||
This class reads the sequences and bone information immediately upon instantiation and hold onto a file handle.
|
||||
The key data is not read into memory upon instantiation due to it's potentially very large size.
|
||||
To read the key data for a particular sequence, call `read_sequence_keys`.
|
||||
"""
|
||||
|
||||
def __init__(self, path):
|
||||
self.keys_data_offset: int = 0
|
||||
self.fp = open(path, 'rb')
|
||||
@@ -22,15 +25,6 @@ class PsaReader(object):
|
||||
def sequences(self) -> OrderedDict[Psa.Sequence]:
|
||||
return self.psa.sequences
|
||||
|
||||
@staticmethod
|
||||
def _read_types(fp, data_class: ctypes.Structure, section: Section, data):
|
||||
buffer_length = section.data_size * section.data_count
|
||||
buffer = fp.read(buffer_length)
|
||||
offset = 0
|
||||
for _ in range(section.data_count):
|
||||
data.append(data_class.from_buffer_copy(buffer, offset))
|
||||
offset += section.data_size
|
||||
|
||||
def read_sequence_data_matrix(self, sequence_name: str):
|
||||
sequence = self.psa.sequences[sequence_name]
|
||||
keys = self.read_sequence_keys(sequence_name)
|
||||
@@ -65,6 +59,15 @@ class PsaReader(object):
|
||||
offset += data_size
|
||||
return keys
|
||||
|
||||
@staticmethod
|
||||
def _read_types(fp, data_class: ctypes.Structure, section: Section, data):
|
||||
buffer_length = section.data_size * section.data_count
|
||||
buffer = fp.read(buffer_length)
|
||||
offset = 0
|
||||
for _ in range(section.data_count):
|
||||
data.append(data_class.from_buffer_copy(buffer, offset))
|
||||
offset += section.data_size
|
||||
|
||||
def _read(self, fp) -> Psa:
|
||||
psa = Psa()
|
||||
while fp.read(1):
|
||||
@@ -88,4 +91,3 @@ class PsaReader(object):
|
||||
else:
|
||||
raise RuntimeError(f'Unrecognized section "{section.name}"')
|
||||
return psa
|
||||
1
|
||||
Reference in New Issue
Block a user