mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2024-11-28 00:20:48 +01:00
* 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:
parent
d56aa3ab65
commit
1eafb71dce
@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "PSK/PSA Importer/Exporter",
|
"name": "PSK/PSA Importer/Exporter",
|
||||||
"author": "Colin Basnett",
|
"author": "Colin Basnett",
|
||||||
"version": (2, 1, 0),
|
"version": (3, 0, 0),
|
||||||
"blender": (2, 80, 0),
|
"blender": (2, 80, 0),
|
||||||
# "location": "File > Export > PSK Export (.psk)",
|
# "location": "File > Export > PSK Export (.psk)",
|
||||||
"description": "PSK/PSA Import/Export (.psk/.psa)",
|
"description": "PSK/PSA Import/Export (.psk/.psa)",
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
from bpy.types import NlaStrip
|
|
||||||
from typing import List, Tuple, Optional
|
|
||||||
from collections import Counter
|
|
||||||
import datetime
|
import datetime
|
||||||
|
from collections import Counter
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from bpy.types import NlaStrip
|
||||||
|
|
||||||
|
|
||||||
class Timer:
|
class Timer:
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
from typing import Dict, Iterable
|
||||||
|
|
||||||
|
from bpy.types import Action
|
||||||
|
|
||||||
from .data import *
|
from .data import *
|
||||||
from ..helpers import *
|
from ..helpers import *
|
||||||
from typing import Dict, Iterable
|
|
||||||
from bpy.types import Action
|
|
||||||
|
|
||||||
|
|
||||||
class PsaBuilderOptions(object):
|
class PsaBuilderOptions(object):
|
||||||
@ -37,13 +39,13 @@ class PsaBuilder(object):
|
|||||||
return options.fps_custom
|
return options.fps_custom
|
||||||
elif options.fps_source == 'ACTION_METADATA':
|
elif options.fps_source == 'ACTION_METADATA':
|
||||||
# Get the minimum value of action metadata FPS values.
|
# Get the minimum value of action metadata FPS values.
|
||||||
psa_fps_list = []
|
fps_list = []
|
||||||
for action in filter(lambda x: 'psa_fps' in x, actions):
|
for action in filter(lambda x: 'psa_sequence_fps' in x, actions):
|
||||||
psa_fps = action['psa_fps']
|
fps = action['psa_sequence_fps']
|
||||||
if type(psa_fps) == int or type(psa_fps) == float:
|
if type(fps) == int or type(fps) == float:
|
||||||
psa_fps_list.append(psa_fps)
|
fps_list.append(fps)
|
||||||
if len(psa_fps_list) > 0:
|
if len(fps_list) > 0:
|
||||||
return min(psa_fps_list)
|
return min(fps_list)
|
||||||
else:
|
else:
|
||||||
# No valid action metadata to use, fallback to scene FPS
|
# No valid action metadata to use, fallback to scene FPS
|
||||||
return context.scene.render.fps
|
return context.scene.render.fps
|
||||||
@ -166,7 +168,8 @@ class PsaBuilder(object):
|
|||||||
export_sequence.nla_state.action = None
|
export_sequence.nla_state.action = None
|
||||||
export_sequence.nla_state.frame_min = frame_min
|
export_sequence.nla_state.frame_min = frame_min
|
||||||
export_sequence.nla_state.frame_max = frame_max
|
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_sequence.fps = self.get_sequence_fps(context, options, nla_strips_actions)
|
||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
else:
|
else:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import typing
|
import typing
|
||||||
from typing import List
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from ..data import *
|
from ..data import *
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import bpy
|
import fnmatch
|
||||||
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 re
|
import re
|
||||||
import sys
|
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):
|
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)
|
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)
|
update_action_names(context)
|
||||||
|
|
||||||
|
|
||||||
@ -68,7 +71,8 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
description='',
|
description='',
|
||||||
items=(
|
items=(
|
||||||
('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
|
('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(
|
fps_source: EnumProperty(
|
||||||
@ -77,11 +81,14 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
description='',
|
description='',
|
||||||
items=(
|
items=(
|
||||||
('SCENE', 'Scene', '', 'SCENE_DATA', 0),
|
('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)
|
('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: CollectionProperty(type=PsaExportActionListItem)
|
||||||
action_list_index: IntProperty(default=0)
|
action_list_index: IntProperty(default=0)
|
||||||
marker_list: CollectionProperty(type=PsaExportTimelineMarkerListItem)
|
marker_list: CollectionProperty(type=PsaExportTimelineMarkerListItem)
|
||||||
@ -117,7 +124,8 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
sequence_name_suffix: StringProperty(name='Suffix', options=set())
|
sequence_name_suffix: StringProperty(name='Suffix', options=set())
|
||||||
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
||||||
sequence_use_filter_invert: BoolProperty(default=False, options=set())
|
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())
|
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set())
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +186,8 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
|
|
||||||
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||||
rows = max(3, min(len(pg.marker_list), 10))
|
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 = layout.column()
|
||||||
col.use_property_split = True
|
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(PsaExportBoneGroupsSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT')
|
||||||
row.operator(PsaExportBoneGroupsDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT')
|
row.operator(PsaExportBoneGroupsDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT')
|
||||||
rows = max(3, min(len(pg.bone_group_list), 10))
|
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):
|
def should_action_be_selected_by_default(self, action):
|
||||||
return action is not None and action.asset_data is None
|
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
|
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 = []
|
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)):
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import bpy
|
|
||||||
import os
|
|
||||||
import numpy as np
|
|
||||||
import re
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from mathutils import Vector, Quaternion, Matrix
|
import os
|
||||||
from .data import Psa
|
import re
|
||||||
from typing import List, AnyStr, Optional
|
from typing import List, Optional
|
||||||
from bpy.types import Operator, Action, UIList, PropertyGroup, Panel, Armature, FileSelectParams
|
|
||||||
from bpy_extras.io_utils import ImportHelper
|
import bpy
|
||||||
|
import numpy as np
|
||||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty
|
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
|
from .reader import PsaReader
|
||||||
|
|
||||||
|
|
||||||
@ -18,7 +20,6 @@ class PsaImportOptions(object):
|
|||||||
self.should_use_fake_user = False
|
self.should_use_fake_user = False
|
||||||
self.should_stash = False
|
self.should_stash = False
|
||||||
self.sequence_names = []
|
self.sequence_names = []
|
||||||
self.should_use_action_name_prefix = False
|
|
||||||
self.should_overwrite = False
|
self.should_overwrite = False
|
||||||
self.should_write_keyframes = True
|
self.should_write_keyframes = True
|
||||||
self.should_write_metadata = True
|
self.should_write_metadata = True
|
||||||
@ -76,7 +77,8 @@ class PsaImporter(object):
|
|||||||
# Report if there are missing bones in the target armature.
|
# Report if there are missing bones in the target armature.
|
||||||
missing_bone_names = set(psa_bone_names).difference(set(armature_bone_names))
|
missing_bone_names = set(psa_bone_names).difference(set(armature_bone_names))
|
||||||
if len(missing_bone_names) > 0:
|
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)))
|
print(list(sorted(missing_bone_names)))
|
||||||
del armature_bone_names
|
del armature_bone_names
|
||||||
|
|
||||||
@ -192,14 +194,16 @@ class PsaImporter(object):
|
|||||||
if bone_has_writeable_keyframes:
|
if bone_has_writeable_keyframes:
|
||||||
# This bone has writeable keyframes for this frame.
|
# This bone has writeable keyframes for this frame.
|
||||||
key_data = sequence_data_matrix[frame_index, bone_index]
|
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:
|
if should_write:
|
||||||
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
|
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
|
||||||
|
|
||||||
# Write
|
# Write
|
||||||
if options.should_write_metadata:
|
if options.should_write_metadata:
|
||||||
action['psa_sequence_name'] = sequence_name
|
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
|
action.use_fake_user = options.should_use_fake_user
|
||||||
|
|
||||||
@ -259,18 +263,28 @@ class PsaImportPropertyGroup(PropertyGroup):
|
|||||||
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', description='Exclude unnecessary keyframes from being written to the actions.', options=set())
|
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes',
|
||||||
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())
|
description='Exclude unnecessary keyframes from being written to the actions',
|
||||||
should_stash: BoolProperty(default=False, name='Stash', description='Stash each imported action as a strip on a new non-contributing NLA track', options=set())
|
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_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())
|
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_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_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)
|
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:
|
except re.error:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# User regular matching
|
# User regular text matching.
|
||||||
for i, sequence in enumerate(sequences):
|
for i, sequence in enumerate(sequences):
|
||||||
if not fnmatch.fnmatch(sequence.action_name, f'*{pg.sequence_filter_name}*'):
|
if not fnmatch.fnmatch(sequence.action_name, f'*{pg.sequence_filter_name}*'):
|
||||||
flt_flags[i] &= ~bitflag_filter_item
|
flt_flags[i] &= ~bitflag_filter_item
|
||||||
@ -308,7 +322,8 @@ 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[PsaImportActionListItem]:
|
def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[
|
||||||
|
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)):
|
||||||
@ -451,21 +466,6 @@ class PSA_PT_ImportPanel_Advanced(Panel):
|
|||||||
col.prop(pg, 'action_name_prefix')
|
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):
|
class PSA_PT_ImportPanel(Panel):
|
||||||
bl_space_type = 'PROPERTIES'
|
bl_space_type = 'PROPERTIES'
|
||||||
bl_region_type = 'WINDOW'
|
bl_region_type = 'WINDOW'
|
||||||
@ -584,7 +584,7 @@ class PsaImportOperator(Operator):
|
|||||||
options.should_clean_keys = pg.should_clean_keys
|
options.should_clean_keys = pg.should_clean_keys
|
||||||
options.should_use_fake_user = pg.should_use_fake_user
|
options.should_use_fake_user = pg.should_use_fake_user
|
||||||
options.should_stash = pg.should_stash
|
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_overwrite = pg.should_overwrite
|
||||||
options.should_write_metadata = pg.should_write_metadata
|
options.should_write_metadata = pg.should_write_metadata
|
||||||
options.should_write_keyframes = pg.should_write_keyframes
|
options.should_write_keyframes = pg.should_write_keyframes
|
||||||
@ -632,7 +632,6 @@ classes = (
|
|||||||
PsaImportFileReload,
|
PsaImportFileReload,
|
||||||
PSA_PT_ImportPanel,
|
PSA_PT_ImportPanel,
|
||||||
PSA_PT_ImportPanel_Advanced,
|
PSA_PT_ImportPanel_Advanced,
|
||||||
PSA_PT_ImportPanel_PsaData,
|
|
||||||
PsaImportOperator,
|
PsaImportOperator,
|
||||||
PsaImportFileSelectOperator,
|
PsaImportFileSelectOperator,
|
||||||
PsaImportSelectFile,
|
PsaImportSelectFile,
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
from .data import *
|
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from .data import *
|
||||||
|
|
||||||
|
|
||||||
class PsaReader(object):
|
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.
|
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`.
|
To read the key data for a particular sequence, call `read_sequence_keys`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.keys_data_offset: int = 0
|
self.keys_data_offset: int = 0
|
||||||
self.fp = open(path, 'rb')
|
self.fp = open(path, 'rb')
|
||||||
@ -22,15 +25,6 @@ class PsaReader(object):
|
|||||||
def sequences(self) -> OrderedDict[Psa.Sequence]:
|
def sequences(self) -> OrderedDict[Psa.Sequence]:
|
||||||
return self.psa.sequences
|
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):
|
def read_sequence_data_matrix(self, sequence_name: str):
|
||||||
sequence = self.psa.sequences[sequence_name]
|
sequence = self.psa.sequences[sequence_name]
|
||||||
keys = self.read_sequence_keys(sequence_name)
|
keys = self.read_sequence_keys(sequence_name)
|
||||||
@ -65,6 +59,15 @@ class PsaReader(object):
|
|||||||
offset += data_size
|
offset += data_size
|
||||||
return keys
|
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:
|
def _read(self, fp) -> Psa:
|
||||||
psa = Psa()
|
psa = Psa()
|
||||||
while fp.read(1):
|
while fp.read(1):
|
||||||
@ -88,4 +91,3 @@ class PsaReader(object):
|
|||||||
else:
|
else:
|
||||||
raise RuntimeError(f'Unrecognized section "{section.name}"')
|
raise RuntimeError(f'Unrecognized section "{section.name}"')
|
||||||
return psa
|
return psa
|
||||||
1
|
|
@ -1,6 +1,5 @@
|
|||||||
import bpy
|
|
||||||
import bmesh
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from .data import *
|
from .data import *
|
||||||
from ..helpers import *
|
from ..helpers import *
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ..data import *
|
from ..data import *
|
||||||
|
|
||||||
|
|
||||||
class Psk(object):
|
class Psk(object):
|
||||||
|
|
||||||
class Wedge(object):
|
class Wedge(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.point_index: int = 0
|
self.point_index: int = 0
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
from .data import *
|
|
||||||
from ..types import BoneGroupListItem
|
|
||||||
from ..helpers import populate_bone_group_list
|
|
||||||
from .builder import PskBuilder, PskBuilderOptions
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
|
from bpy.props import StringProperty, CollectionProperty, IntProperty, EnumProperty
|
||||||
from bpy.types import Operator, PropertyGroup
|
from bpy.types import Operator, PropertyGroup
|
||||||
from bpy_extras.io_utils import ExportHelper
|
from bpy_extras.io_utils import ExportHelper
|
||||||
from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty, EnumProperty
|
|
||||||
|
from .builder import PskBuilder, PskBuilderOptions
|
||||||
|
from .data import *
|
||||||
|
from ..helpers import populate_bone_group_list
|
||||||
|
from ..types import BoneGroupListItem
|
||||||
|
|
||||||
MAX_WEDGE_COUNT = 65536
|
MAX_WEDGE_COUNT = 65536
|
||||||
MAX_POINT_COUNT = 4294967296
|
MAX_POINT_COUNT = 4294967296
|
||||||
@ -144,7 +146,8 @@ class PskExportPropertyGroup(PropertyGroup):
|
|||||||
description='',
|
description='',
|
||||||
items=(
|
items=(
|
||||||
('ALL', 'All', 'All bones will be exported.'),
|
('ALL', 'All', 'All bones will be exported.'),
|
||||||
('BONE_GROUPS', 'Bone Groups', 'Only bones belonging to the selected bone groups and their ancestors will be exported.')
|
('BONE_GROUPS', 'Bone Groups',
|
||||||
|
'Only bones belonging to the selected bone groups and their ancestors will be exported.')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
bone_group_list: CollectionProperty(type=BoneGroupListItem)
|
bone_group_list: CollectionProperty(type=BoneGroupListItem)
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import os
|
import os
|
||||||
import bpy
|
import sys
|
||||||
import bmesh
|
|
||||||
import numpy as np
|
|
||||||
from math import inf
|
from math import inf
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from .data import Psk
|
|
||||||
from ..helpers import rgb_to_srgb
|
import bmesh
|
||||||
from mathutils import Quaternion, Vector, Matrix
|
import bpy
|
||||||
from .reader import PskReader
|
import numpy as np
|
||||||
from bpy.props import StringProperty, EnumProperty, BoolProperty
|
from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty
|
||||||
from bpy.types import Operator, PropertyGroup
|
from bpy.types import Operator, PropertyGroup
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
|
from mathutils import Quaternion, Vector, Matrix
|
||||||
|
|
||||||
|
from .data import Psk
|
||||||
|
from .reader import PskReader
|
||||||
|
from ..helpers import rgb_to_srgb
|
||||||
|
|
||||||
|
|
||||||
class PskImportOptions(object):
|
class PskImportOptions(object):
|
||||||
@ -20,6 +23,7 @@ class PskImportOptions(object):
|
|||||||
self.vertex_color_space = 'sRGB'
|
self.vertex_color_space = 'sRGB'
|
||||||
self.should_import_vertex_normals = True
|
self.should_import_vertex_normals = True
|
||||||
self.should_import_extra_uvs = True
|
self.should_import_extra_uvs = True
|
||||||
|
self.bone_length = 1.0
|
||||||
|
|
||||||
|
|
||||||
class PskImporter(object):
|
class PskImporter(object):
|
||||||
@ -60,7 +64,6 @@ class PskImporter(object):
|
|||||||
self.post_quat: Quaternion = Quaternion()
|
self.post_quat: Quaternion = Quaternion()
|
||||||
|
|
||||||
import_bones = []
|
import_bones = []
|
||||||
new_bone_size = 8.0
|
|
||||||
|
|
||||||
for bone_index, psk_bone in enumerate(psk.bones):
|
for bone_index, psk_bone in enumerate(psk.bones):
|
||||||
import_bone = ImportBone(bone_index, psk_bone)
|
import_bone = ImportBone(bone_index, psk_bone)
|
||||||
@ -93,7 +96,7 @@ class PskImporter(object):
|
|||||||
else:
|
else:
|
||||||
import_bone.local_rotation.conjugate()
|
import_bone.local_rotation.conjugate()
|
||||||
|
|
||||||
edit_bone.tail = Vector((0.0, new_bone_size, 0.0))
|
edit_bone.tail = Vector((0.0, options.bone_length, 0.0))
|
||||||
edit_bone_matrix = import_bone.local_rotation.conjugated()
|
edit_bone_matrix = import_bone.local_rotation.conjugated()
|
||||||
edit_bone_matrix.rotate(import_bone.world_matrix)
|
edit_bone_matrix.rotate(import_bone.world_matrix)
|
||||||
edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4()
|
edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4()
|
||||||
@ -209,7 +212,8 @@ class PskImporter(object):
|
|||||||
# 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))
|
||||||
for import_bone in map(lambda x: import_bones[x], sorted(list(vertex_group_bone_indices))):
|
for import_bone in map(lambda x: import_bones[x], sorted(list(vertex_group_bone_indices))):
|
||||||
import_bone.vertex_group = mesh_object.vertex_groups.new(name=import_bone.psk_bone.name.decode('windows-1252'))
|
import_bone.vertex_group = mesh_object.vertex_groups.new(
|
||||||
|
name=import_bone.psk_bone.name.decode('windows-1252'))
|
||||||
|
|
||||||
for weight in psk.weights:
|
for weight in psk.weights:
|
||||||
import_bones[weight.bone_index].vertex_group.add((weight.point_index,), weight.weight, 'ADD')
|
import_bones[weight.bone_index].vertex_group.add((weight.point_index,), weight.weight, 'ADD')
|
||||||
@ -256,6 +260,15 @@ class PskImportPropertyGroup(PropertyGroup):
|
|||||||
options=set(),
|
options=set(),
|
||||||
description='Import extra UV maps from PSKX files, if available'
|
description='Import extra UV maps from PSKX files, if available'
|
||||||
)
|
)
|
||||||
|
bone_length: FloatProperty(
|
||||||
|
default=1.0,
|
||||||
|
min=sys.float_info.epsilon,
|
||||||
|
step=100,
|
||||||
|
soft_min=1.0,
|
||||||
|
name='Bone Length',
|
||||||
|
options=set(),
|
||||||
|
description='Length of the bones'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PskImportOperator(Operator, ImportHelper):
|
class PskImportOperator(Operator, ImportHelper):
|
||||||
@ -277,7 +290,11 @@ class PskImportOperator(Operator, ImportHelper):
|
|||||||
psk = reader.read(self.filepath)
|
psk = reader.read(self.filepath)
|
||||||
options = PskImportOptions()
|
options = PskImportOptions()
|
||||||
options.name = os.path.splitext(os.path.basename(self.filepath))[0]
|
options.name = os.path.splitext(os.path.basename(self.filepath))[0]
|
||||||
|
options.should_import_extra_uvs = pg.should_import_extra_uvs
|
||||||
|
options.should_import_vertex_colors = pg.should_import_vertex_colors
|
||||||
|
options.should_import_vertex_normals = pg.should_import_vertex_normals
|
||||||
options.vertex_color_space = pg.vertex_color_space
|
options.vertex_color_space = pg.vertex_color_space
|
||||||
|
options.bone_length = pg.bone_length
|
||||||
PskImporter().import_psk(psk, context, options)
|
PskImporter().import_psk(psk, context, options)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -291,6 +308,7 @@ class PskImportOperator(Operator, ImportHelper):
|
|||||||
layout.prop(pg, 'should_import_vertex_colors')
|
layout.prop(pg, 'should_import_vertex_colors')
|
||||||
if pg.should_import_vertex_colors:
|
if pg.should_import_vertex_colors:
|
||||||
layout.prop(pg, 'vertex_color_space')
|
layout.prop(pg, 'vertex_color_space')
|
||||||
|
layout.prop(pg, 'bone_length')
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from .data import *
|
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
|
from .data import *
|
||||||
|
|
||||||
|
|
||||||
class PskReader(object):
|
class PskReader(object):
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from bpy.types import PropertyGroup, UIList
|
|
||||||
from bpy.props import StringProperty, IntProperty, BoolProperty
|
from bpy.props import StringProperty, IntProperty, BoolProperty
|
||||||
|
from bpy.types import PropertyGroup, UIList
|
||||||
|
|
||||||
|
|
||||||
class PSX_UL_BoneGroupList(UIList):
|
class PSX_UL_BoneGroupList(UIList):
|
||||||
|
Loading…
Reference in New Issue
Block a user