Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
980042fc7f | ||
|
|
02082b9594 | ||
|
|
4181a15d0e | ||
|
|
b6ef3dda44 | ||
|
|
f7290e6808 | ||
|
|
65d3104ea9 | ||
|
|
1a48128cb9 |
@@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "PSK/PSA Importer/Exporter",
|
"name": "PSK/PSA Importer/Exporter",
|
||||||
"author": "Colin Basnett, Yurii Ti",
|
"author": "Colin Basnett, Yurii Ti",
|
||||||
"version": (4, 0, 1),
|
"version": (4, 1, 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)",
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ def check_bone_names(bone_names: Iterable[str]):
|
|||||||
invalid_bone_names = [x for x in bone_names if pattern.match(x) is None]
|
invalid_bone_names = [x for x in bone_names if pattern.match(x) is None]
|
||||||
if len(invalid_bone_names) > 0:
|
if len(invalid_bone_names) > 0:
|
||||||
raise RuntimeError(f'The following bone names are invalid: {invalid_bone_names}.\n'
|
raise RuntimeError(f'The following bone names are invalid: {invalid_bone_names}.\n'
|
||||||
f'Bone names must only contain letters, numbers, spaces, and underscores.')
|
f'Bone names must only contain letters, numbers, spaces, hyphens and underscores.')
|
||||||
|
|
||||||
|
|
||||||
def get_export_bone_names(armature_object, bone_filter_mode, bone_group_indices: List[int]) -> List[str]:
|
def get_export_bone_names(armature_object, bone_filter_mode, bone_group_indices: List[int]) -> List[str]:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from bpy.types import Action
|
from bpy.types import Action, Armature, Bone
|
||||||
|
|
||||||
from .data import *
|
from .data import *
|
||||||
from ..helpers import *
|
from ..helpers import *
|
||||||
@@ -19,6 +19,7 @@ class PsaBuildOptions(object):
|
|||||||
self.bone_group_indices = []
|
self.bone_group_indices = []
|
||||||
self.should_use_original_sequence_names = False
|
self.should_use_original_sequence_names = False
|
||||||
self.should_trim_timeline_marker_sequences = True
|
self.should_trim_timeline_marker_sequences = True
|
||||||
|
self.should_ignore_bone_name_restrictions = False
|
||||||
self.sequence_name_prefix = ''
|
self.sequence_name_prefix = ''
|
||||||
self.sequence_name_suffix = ''
|
self.sequence_name_suffix = ''
|
||||||
self.root_motion = False
|
self.root_motion = False
|
||||||
@@ -87,7 +88,7 @@ def get_timeline_marker_sequence_frame_ranges(animation_data, context, options:
|
|||||||
return sequence_frame_ranges
|
return sequence_frame_ranges
|
||||||
|
|
||||||
|
|
||||||
def build_psa(context, options: PsaBuildOptions) -> Psa:
|
def build_psa(context: bpy.types.Context, options: PsaBuildOptions) -> Psa:
|
||||||
active_object = context.view_layer.objects.active
|
active_object = context.view_layer.objects.active
|
||||||
|
|
||||||
if active_object.type != 'ARMATURE':
|
if active_object.type != 'ARMATURE':
|
||||||
@@ -112,7 +113,8 @@ def build_psa(context, options: PsaBuildOptions) -> Psa:
|
|||||||
psa = Psa()
|
psa = Psa()
|
||||||
|
|
||||||
armature = active_object
|
armature = active_object
|
||||||
bones = list(armature.data.bones)
|
armature_data = typing.cast(Armature, armature)
|
||||||
|
bones: List[Bone] = list(iter(armature_data.bones))
|
||||||
|
|
||||||
# The order of the armature bones and the pose bones is not guaranteed to be the same.
|
# The order of the armature bones and the pose bones is not guaranteed to be the same.
|
||||||
# As a result, we need to reconstruct the list of pose bones in the same order as the
|
# As a result, we need to reconstruct the list of pose bones in the same order as the
|
||||||
@@ -135,7 +137,8 @@ def build_psa(context, options: PsaBuildOptions) -> Psa:
|
|||||||
raise RuntimeError('No bones available for export')
|
raise RuntimeError('No bones available for export')
|
||||||
|
|
||||||
# Check that all bone names are valid.
|
# Check that all bone names are valid.
|
||||||
check_bone_names(map(lambda bone: bone.name, bones))
|
if not options.should_ignore_bone_name_restrictions:
|
||||||
|
check_bone_names(map(lambda bone: bone.name, bones))
|
||||||
|
|
||||||
# Build list of PSA bones.
|
# Build list of PSA bones.
|
||||||
for bone in bones:
|
for bone in bones:
|
||||||
@@ -208,6 +211,7 @@ def build_psa(context, options: PsaBuildOptions) -> Psa:
|
|||||||
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(
|
nla_strips_actions = set(
|
||||||
map(lambda x: x.action, get_nla_strips_in_timeframe(animation_data, frame_min, frame_max)))
|
map(lambda x: x.action, get_nla_strips_in_timeframe(animation_data, frame_min, frame_max)))
|
||||||
export_sequence.fps = get_sequence_fps(context, options, nla_strips_actions)
|
export_sequence.fps = get_sequence_fps(context, options, nla_strips_actions)
|
||||||
@@ -220,6 +224,10 @@ def build_psa(context, options: PsaBuildOptions) -> Psa:
|
|||||||
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'
|
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'
|
||||||
export_sequence.name = export_sequence.name.strip()
|
export_sequence.name = export_sequence.name.strip()
|
||||||
|
|
||||||
|
# Save the current action and frame so that we can restore the state once we are done.
|
||||||
|
saved_frame_current = context.scene.frame_current
|
||||||
|
saved_action = animation_data.action
|
||||||
|
|
||||||
# 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.
|
||||||
frame_start_index = 0
|
frame_start_index = 0
|
||||||
@@ -280,4 +288,8 @@ def build_psa(context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
psa.sequences[export_sequence.name] = psa_sequence
|
psa.sequences[export_sequence.name] = psa_sequence
|
||||||
|
|
||||||
|
# Restore the previous action & frame.
|
||||||
|
animation_data.action = saved_action
|
||||||
|
context.scene.frame_set(saved_frame_current)
|
||||||
|
|
||||||
return psa
|
return psa
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from ..data import *
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Note that keys are not stored within the Psa object.
|
Note that keys are not stored within the Psa object.
|
||||||
Use the PsaReader::get_sequence_keys to get a the keys for a sequence.
|
Use the PsaReader::get_sequence_keys to get the keys for a sequence.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -60,5 +60,5 @@ class Psa(object):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bones: List[Psa.Bone] = []
|
self.bones: List[Psa.Bone] = []
|
||||||
self.sequences: typing.OrderedDict[Psa.Sequence] = OrderedDict()
|
self.sequences: typing.OrderedDict[str, Psa.Sequence] = OrderedDict()
|
||||||
self.keys: List[Psa.Key] = []
|
self.keys: List[Psa.Key] = []
|
||||||
|
|||||||
@@ -136,6 +136,12 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
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 '
|
||||||
)
|
)
|
||||||
|
should_ignore_bone_name_restrictions: BoolProperty(
|
||||||
|
default=False,
|
||||||
|
name='Ignore Bone Name Restrictions',
|
||||||
|
description='Bone names restrictions will be ignored. Note that bone names without properly formatted names '
|
||||||
|
'cannot be referenced in scripts.'
|
||||||
|
)
|
||||||
sequence_name_prefix: StringProperty(name='Prefix', options=empty_set)
|
sequence_name_prefix: StringProperty(name='Prefix', options=empty_set)
|
||||||
sequence_name_suffix: StringProperty(name='Suffix', options=empty_set)
|
sequence_name_suffix: StringProperty(name='Suffix', options=empty_set)
|
||||||
sequence_filter_name: StringProperty(
|
sequence_filter_name: StringProperty(
|
||||||
@@ -262,6 +268,8 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index',
|
layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index',
|
||||||
rows=rows)
|
rows=rows)
|
||||||
|
|
||||||
|
layout.prop(pg, 'should_ignore_bone_name_restrictions')
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
# ROOT MOTION
|
# ROOT MOTION
|
||||||
@@ -346,6 +354,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
options.bone_group_indices = [x.index for x in pg.bone_group_list if x.is_selected]
|
options.bone_group_indices = [x.index for x in pg.bone_group_list if x.is_selected]
|
||||||
options.should_use_original_sequence_names = pg.should_use_original_sequence_names
|
options.should_use_original_sequence_names = pg.should_use_original_sequence_names
|
||||||
options.should_trim_timeline_marker_sequences = pg.should_trim_timeline_marker_sequences
|
options.should_trim_timeline_marker_sequences = pg.should_trim_timeline_marker_sequences
|
||||||
|
options.should_ignore_bone_name_restrictions = pg.should_ignore_bone_name_restrictions
|
||||||
options.sequence_name_prefix = pg.sequence_name_prefix
|
options.sequence_name_prefix = pg.sequence_name_prefix
|
||||||
options.sequence_name_suffix = pg.sequence_name_suffix
|
options.sequence_name_suffix = pg.sequence_name_suffix
|
||||||
options.root_motion = pg.root_motion
|
options.root_motion = pg.root_motion
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import re
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import bpy
|
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.types import Operator, UIList, PropertyGroup, Panel
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
@@ -16,7 +15,6 @@ from .reader import PsaReader
|
|||||||
|
|
||||||
class PsaImportOptions(object):
|
class PsaImportOptions(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.should_clean_keys = True
|
|
||||||
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 = []
|
||||||
@@ -24,6 +22,7 @@ class PsaImportOptions(object):
|
|||||||
self.should_write_keyframes = True
|
self.should_write_keyframes = True
|
||||||
self.should_write_metadata = True
|
self.should_write_metadata = True
|
||||||
self.action_name_prefix = ''
|
self.action_name_prefix = ''
|
||||||
|
self.should_convert_to_samples = False
|
||||||
|
|
||||||
|
|
||||||
class ImportBone(object):
|
class ImportBone(object):
|
||||||
@@ -154,7 +153,6 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
|
|||||||
|
|
||||||
# Read the sequence data matrix from the PSA.
|
# Read the sequence data matrix from the PSA.
|
||||||
sequence_data_matrix = psa_reader.read_sequence_data_matrix(sequence_name)
|
sequence_data_matrix = psa_reader.read_sequence_data_matrix(sequence_name)
|
||||||
keyframe_write_matrix = np.ones(sequence_data_matrix.shape, dtype=np.int8)
|
|
||||||
|
|
||||||
# Convert the sequence's data from world-space to local-space.
|
# Convert the sequence's data from world-space to local-space.
|
||||||
for bone_index, import_bone in enumerate(import_bones):
|
for bone_index, import_bone in enumerate(import_bones):
|
||||||
@@ -166,39 +164,18 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
|
|||||||
# Calculate the local-space key data for the bone.
|
# Calculate the local-space key data for the bone.
|
||||||
sequence_data_matrix[frame_index, bone_index] = calculate_fcurve_data(import_bone, key_data)
|
sequence_data_matrix[frame_index, bone_index] = calculate_fcurve_data(import_bone, key_data)
|
||||||
|
|
||||||
# Clean the keyframe data. This is accomplished by writing zeroes to the write matrix when there is an
|
|
||||||
# insufficiently large change in the data from the last written frame.
|
|
||||||
if options.should_clean_keys:
|
|
||||||
threshold = 0.001
|
|
||||||
for bone_index, import_bone in enumerate(import_bones):
|
|
||||||
if import_bone is None:
|
|
||||||
continue
|
|
||||||
for fcurve_index in range(len(import_bone.fcurves)):
|
|
||||||
# Get all the keyframe data for the bone's f-curve data from the sequence data matrix.
|
|
||||||
fcurve_frame_data = sequence_data_matrix[:, bone_index, fcurve_index]
|
|
||||||
last_written_datum = 0
|
|
||||||
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 frame_index > 0 and abs(datum - last_written_datum) < threshold:
|
|
||||||
keyframe_write_matrix[frame_index, bone_index, fcurve_index] = 0
|
|
||||||
else:
|
|
||||||
last_written_datum = datum
|
|
||||||
|
|
||||||
# Write the keyframes out!
|
# Write the keyframes out!
|
||||||
for frame_index in range(sequence.frame_count):
|
for frame_index in range(sequence.frame_count):
|
||||||
for bone_index, import_bone in enumerate(import_bones):
|
for bone_index, import_bone in enumerate(import_bones):
|
||||||
if import_bone is None:
|
if import_bone is None:
|
||||||
continue
|
continue
|
||||||
bone_has_writeable_keyframes = any(keyframe_write_matrix[frame_index, bone_index])
|
key_data = sequence_data_matrix[frame_index, bone_index]
|
||||||
if bone_has_writeable_keyframes:
|
for fcurve, datum in zip(import_bone.fcurves, key_data):
|
||||||
# This bone has writeable keyframes for this frame.
|
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
|
||||||
key_data = sequence_data_matrix[frame_index, bone_index]
|
|
||||||
for fcurve, should_write, datum in zip(import_bone.fcurves,
|
if options.should_convert_to_samples:
|
||||||
keyframe_write_matrix[frame_index, bone_index],
|
for fcurve in action.fcurves:
|
||||||
key_data):
|
fcurve.convert_to_samples(start=0, end=sequence.frame_count)
|
||||||
if should_write:
|
|
||||||
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
|
|
||||||
|
|
||||||
# Write
|
# Write
|
||||||
if options.should_write_metadata:
|
if options.should_write_metadata:
|
||||||
@@ -266,9 +243,6 @@ 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=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=empty_set)
|
options=empty_set)
|
||||||
@@ -289,6 +263,11 @@ class PsaImportPropertyGroup(PropertyGroup):
|
|||||||
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=empty_set)
|
description='Filter using regular expressions', options=empty_set)
|
||||||
select_text: PointerProperty(type=bpy.types.Text)
|
select_text: PointerProperty(type=bpy.types.Text)
|
||||||
|
should_convert_to_samples: BoolProperty(
|
||||||
|
default=True,
|
||||||
|
name='Convert to Samples',
|
||||||
|
description='Convert keyframes to read-only samples. Recommended if you do not plan on editing the actions directly'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def filter_sequences(pg: PsaImportPropertyGroup, sequences) -> List[int]:
|
def filter_sequences(pg: PsaImportPropertyGroup, sequences) -> List[int]:
|
||||||
@@ -457,11 +436,14 @@ class PSA_PT_ImportPanel_Advanced(Panel):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
pg = getattr(context.scene, 'psa_import')
|
pg = getattr(context.scene, 'psa_import')
|
||||||
|
|
||||||
col = layout.column(heading="Options")
|
col = layout.column(heading='Keyframes')
|
||||||
col.use_property_split = True
|
col.use_property_split = True
|
||||||
col.use_property_decorate = False
|
col.use_property_decorate = False
|
||||||
col.prop(pg, 'should_clean_keys')
|
col.prop(pg, 'should_convert_to_samples')
|
||||||
col.separator()
|
col.separator()
|
||||||
|
col = layout.column(heading='Options')
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
col.prop(pg, 'should_use_fake_user')
|
col.prop(pg, 'should_use_fake_user')
|
||||||
col.prop(pg, 'should_stash')
|
col.prop(pg, 'should_stash')
|
||||||
col.prop(pg, 'should_use_action_name_prefix')
|
col.prop(pg, 'should_use_action_name_prefix')
|
||||||
@@ -585,13 +567,13 @@ class PsaImportOperator(Operator):
|
|||||||
|
|
||||||
options = PsaImportOptions()
|
options = PsaImportOptions()
|
||||||
options.sequence_names = sequence_names
|
options.sequence_names = sequence_names
|
||||||
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 if pg.should_use_action_name_prefix else ''
|
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
|
||||||
|
options.should_convert_to_samples = pg.should_convert_to_samples
|
||||||
|
|
||||||
import_psa(psa_reader, context.view_layer.objects.active, options)
|
import_psa(psa_reader, context.view_layer.objects.active, options)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ class PsaReader(object):
|
|||||||
def sequences(self):
|
def sequences(self):
|
||||||
return self.psa.sequences
|
return self.psa.sequences
|
||||||
|
|
||||||
def read_sequence_data_matrix(self, sequence_name: str):
|
def read_sequence_data_matrix(self, sequence_name: str) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Reads and returns the data matrix for the given sequence.
|
||||||
|
@param sequence_name: The name of the sequence.
|
||||||
|
@return: An FxBx7 matrix where F is the number of frames, B is the number of bones.
|
||||||
|
"""
|
||||||
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)
|
||||||
bone_count = len(self.bones)
|
bone_count = len(self.bones)
|
||||||
@@ -41,8 +46,8 @@ class PsaReader(object):
|
|||||||
"""
|
"""
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
# Set the file reader to the beginning of the keys data
|
# Set the file reader to the beginning of the keys data
|
||||||
sequence = self.psa.sequences[sequence_name]
|
sequence = self.psa.sequences[sequence_name]
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
from collections import OrderedDict
|
import bmesh
|
||||||
from typing import Dict, List
|
import bpy
|
||||||
|
|
||||||
from .data import *
|
from .data import *
|
||||||
from ..helpers import *
|
from ..helpers import *
|
||||||
import bmesh
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
|
|
||||||
class PskInputObjects(object):
|
class PskInputObjects(object):
|
||||||
@@ -19,6 +17,7 @@ class PskBuildOptions(object):
|
|||||||
self.bone_group_indices: List[int] = []
|
self.bone_group_indices: List[int] = []
|
||||||
self.use_raw_mesh_data = True
|
self.use_raw_mesh_data = True
|
||||||
self.material_names: List[str] = []
|
self.material_names: List[str] = []
|
||||||
|
self.should_ignore_bone_name_restrictions = False
|
||||||
|
|
||||||
|
|
||||||
def get_psk_input_objects(context) -> PskInputObjects:
|
def get_psk_input_objects(context) -> PskInputObjects:
|
||||||
@@ -81,7 +80,8 @@ def build_psk(context, options: PskBuildOptions) -> Psk:
|
|||||||
bones = [armature_object.data.bones[bone_name] for bone_name in bone_names]
|
bones = [armature_object.data.bones[bone_name] for bone_name in bone_names]
|
||||||
|
|
||||||
# Check that all bone names are valid.
|
# Check that all bone names are valid.
|
||||||
check_bone_names(map(lambda x: x.name, bones))
|
if not options.should_ignore_bone_name_restrictions:
|
||||||
|
check_bone_names(map(lambda x: x.name, bones))
|
||||||
|
|
||||||
for bone in bones:
|
for bone in bones:
|
||||||
psk_bone = Psk.Bone()
|
psk_bone = Psk.Bone()
|
||||||
@@ -133,16 +133,17 @@ def build_psk(context, options: PskBuildOptions) -> Psk:
|
|||||||
# MATERIALS
|
# MATERIALS
|
||||||
material_indices = [material_names.index(material.name) for material in input_mesh_object.data.materials]
|
material_indices = [material_names.index(material.name) for material in input_mesh_object.data.materials]
|
||||||
|
|
||||||
|
# MESH DATA
|
||||||
if options.use_raw_mesh_data:
|
if options.use_raw_mesh_data:
|
||||||
mesh_object = input_mesh_object
|
mesh_object = input_mesh_object
|
||||||
mesh_data = input_mesh_object.data
|
mesh_data = input_mesh_object.data
|
||||||
else:
|
else:
|
||||||
# Create a copy of the mesh object after non-armature modifiers are applied.
|
# Create a copy of the mesh object after non-armature modifiers are applied.
|
||||||
|
|
||||||
# Temporarily deactivate any armature modifiers on the input mesh object.
|
# Temporarily force the armature into the rest position.
|
||||||
active_armature_modifiers = [x for x in filter(lambda x: x.type == 'ARMATURE' and x.is_active, input_mesh_object.modifiers)]
|
# We will undo this later.
|
||||||
for modifier in active_armature_modifiers:
|
old_pose_position = armature_object.data.pose_position
|
||||||
modifier.show_viewport = False
|
armature_object.data.pose_position = 'REST'
|
||||||
|
|
||||||
depsgraph = context.evaluated_depsgraph_get()
|
depsgraph = context.evaluated_depsgraph_get()
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
@@ -157,9 +158,8 @@ def build_psk(context, options: PskBuildOptions) -> Psk:
|
|||||||
for vertex_group in input_mesh_object.vertex_groups:
|
for vertex_group in input_mesh_object.vertex_groups:
|
||||||
mesh_object.vertex_groups.new(name=vertex_group.name)
|
mesh_object.vertex_groups.new(name=vertex_group.name)
|
||||||
|
|
||||||
# Reactivate previously active armature modifiers
|
# Restore the previous pose position on the armature.
|
||||||
for modifier in active_armature_modifiers:
|
armature_object.data.pose_position = old_pose_position
|
||||||
modifier.show_viewport = True
|
|
||||||
|
|
||||||
vertex_offset = len(psk.points)
|
vertex_offset = len(psk.points)
|
||||||
|
|
||||||
|
|||||||
@@ -208,6 +208,10 @@ class PskExportOperator(Operator, ExportHelper):
|
|||||||
col.operator(PskMaterialListItemMoveUp.bl_idname, text='', icon='TRIA_UP')
|
col.operator(PskMaterialListItemMoveUp.bl_idname, text='', icon='TRIA_UP')
|
||||||
col.operator(PskMaterialListItemMoveDown.bl_idname, text='', icon='TRIA_DOWN')
|
col.operator(PskMaterialListItemMoveDown.bl_idname, text='', icon='TRIA_DOWN')
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
layout.prop(pg, 'should_ignore_bone_name_restrictions')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psk_export
|
pg = context.scene.psk_export
|
||||||
options = PskBuildOptions()
|
options = PskBuildOptions()
|
||||||
@@ -215,6 +219,7 @@ class PskExportOperator(Operator, ExportHelper):
|
|||||||
options.bone_group_indices = [x.index for x in pg.bone_group_list if x.is_selected]
|
options.bone_group_indices = [x.index for x in pg.bone_group_list if x.is_selected]
|
||||||
options.use_raw_mesh_data = pg.use_raw_mesh_data
|
options.use_raw_mesh_data = pg.use_raw_mesh_data
|
||||||
options.material_names = [m.material_name for m in pg.material_list]
|
options.material_names = [m.material_name for m in pg.material_list]
|
||||||
|
options.should_ignore_bone_name_restrictions = pg.should_ignore_bone_name_restrictions
|
||||||
|
|
||||||
try:
|
try:
|
||||||
psk = build_psk(context, options)
|
psk = build_psk(context, options)
|
||||||
@@ -242,6 +247,12 @@ class PskExportPropertyGroup(PropertyGroup):
|
|||||||
use_raw_mesh_data: BoolProperty(default=False, name='Raw Mesh Data', description='No modifiers will be evaluated as part of the exported mesh')
|
use_raw_mesh_data: BoolProperty(default=False, name='Raw Mesh Data', description='No modifiers will be evaluated as part of the exported mesh')
|
||||||
material_list: CollectionProperty(type=MaterialListItem)
|
material_list: CollectionProperty(type=MaterialListItem)
|
||||||
material_list_index: IntProperty(default=0)
|
material_list_index: IntProperty(default=0)
|
||||||
|
should_ignore_bone_name_restrictions: BoolProperty(
|
||||||
|
default=False,
|
||||||
|
name='Ignore Bone Name Restrictions',
|
||||||
|
description='Bone names restrictions will be ignored. Note that bone names without properly formatted names '
|
||||||
|
'cannot be referenced in scripts.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
|||||||
Reference in New Issue
Block a user