Compare commits
1 Commits
psa-collec
...
multi-arma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e704069763 |
@@ -1,10 +1,10 @@
|
|||||||
from bpy.types import Action, AnimData, Context, Object, PoseBone
|
from bpy.types import Action, AnimData, Context, Object, PoseBone
|
||||||
|
|
||||||
from .data import Psa
|
from .data import Psa
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple, Iterable
|
||||||
from mathutils import Matrix, Quaternion, Vector
|
from mathutils import Matrix, Quaternion, Vector
|
||||||
|
|
||||||
from ..shared.helpers import create_psx_bones, get_coordinate_system_transform
|
from ..shared.helpers import PsxBoneCollection, create_psx_bones, get_coordinate_system_transform
|
||||||
|
|
||||||
|
|
||||||
class PsaBuildSequence:
|
class PsaBuildSequence:
|
||||||
@@ -14,8 +14,8 @@ class PsaBuildSequence:
|
|||||||
self.frame_start: int = 0
|
self.frame_start: int = 0
|
||||||
self.frame_end: int = 0
|
self.frame_end: int = 0
|
||||||
|
|
||||||
def __init__(self, armature_object: Object, anim_data: AnimData):
|
def __init__(self, armature_objects: Iterable[Object], anim_data: AnimData):
|
||||||
self.armature_object = armature_object
|
self.armature_objects = list(armature_objects)
|
||||||
self.anim_data = anim_data
|
self.anim_data = anim_data
|
||||||
self.name: str = ''
|
self.name: str = ''
|
||||||
self.nla_state: PsaBuildSequence.NlaState = PsaBuildSequence.NlaState()
|
self.nla_state: PsaBuildSequence.NlaState = PsaBuildSequence.NlaState()
|
||||||
@@ -27,10 +27,9 @@ class PsaBuildSequence:
|
|||||||
class PsaBuildOptions:
|
class PsaBuildOptions:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.armature_objects: List[Object] = []
|
self.armature_objects: List[Object] = []
|
||||||
self.animation_data: Optional[AnimData] = None
|
|
||||||
self.sequences: List[PsaBuildSequence] = []
|
self.sequences: List[PsaBuildSequence] = []
|
||||||
self.bone_filter_mode: str = 'ALL'
|
self.bone_filter_mode: str = 'ALL'
|
||||||
self.bone_collection_indices: List[PsaBoneCollectionIndex] = []
|
self.bone_collection_indices: List[PsxBoneCollection] = []
|
||||||
self.sequence_name_prefix: str = ''
|
self.sequence_name_prefix: str = ''
|
||||||
self.sequence_name_suffix: str = ''
|
self.sequence_name_suffix: str = ''
|
||||||
self.scale = 1.0
|
self.scale = 1.0
|
||||||
@@ -58,7 +57,7 @@ def _get_pose_bone_location_and_rotation(
|
|||||||
|
|
||||||
if is_false_root_bone:
|
if is_false_root_bone:
|
||||||
pose_bone_matrix = coordinate_system_transform
|
pose_bone_matrix = coordinate_system_transform
|
||||||
elif pose_bone.parent is not None:
|
elif pose_bone is not None and pose_bone.parent is not None:
|
||||||
pose_bone_matrix = pose_bone.matrix
|
pose_bone_matrix = pose_bone.matrix
|
||||||
pose_bone_parent_matrix = pose_bone.parent.matrix
|
pose_bone_parent_matrix = pose_bone.parent.matrix
|
||||||
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
|
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
|
||||||
@@ -109,6 +108,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
psa = Psa()
|
psa = Psa()
|
||||||
|
|
||||||
|
# TODO: move this OUT!
|
||||||
armature_objects_for_bones = options.armature_objects
|
armature_objects_for_bones = options.armature_objects
|
||||||
if options.sequence_source == 'ACTIVE_ACTION' and len(options.armature_objects) >= 2:
|
if options.sequence_source == 'ACTIVE_ACTION' and len(options.armature_objects) >= 2:
|
||||||
# Make sure that the data-block for all the selected armature objects is the same.
|
# Make sure that the data-block for all the selected armature objects is the same.
|
||||||
@@ -143,7 +143,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
export_sequence.name = export_sequence.name.strip()
|
export_sequence.name = export_sequence.name.strip()
|
||||||
|
|
||||||
# Save each armature object's current action and frame so that we can restore the state once we are done.
|
# Save each armature object's current action and frame so that we can restore the state once we are done.
|
||||||
saved_armature_object_actions = {o: o.animation_data.action for o in options.armature_objects}
|
saved_armature_object_actions = {o: o.animation_data.action if o.animation_data else None for o in options.armature_objects}
|
||||||
saved_frame_current = context.scene.frame_current
|
saved_frame_current = context.scene.frame_current
|
||||||
|
|
||||||
# Now build the PSA sequences.
|
# Now build the PSA sequences.
|
||||||
@@ -184,11 +184,10 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
psa_sequence.key_reduction = 1.0
|
psa_sequence.key_reduction = 1.0
|
||||||
|
|
||||||
frame = float(frame_start)
|
frame = float(frame_start)
|
||||||
|
|
||||||
|
export_sequence.anim_data.action = export_sequence.nla_state.action
|
||||||
|
|
||||||
# Link the action to the animation data and update view layer.
|
assert context.view_layer
|
||||||
for armature_object in options.armature_objects:
|
|
||||||
armature_object.animation_data.action = export_sequence.nla_state.action
|
|
||||||
|
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
|
|
||||||
def add_key(location: Vector, rotation: Quaternion):
|
def add_key(location: Vector, rotation: Quaternion):
|
||||||
@@ -212,7 +211,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
armature_scales: Dict[Object, Vector] = {}
|
armature_scales: Dict[Object, Vector] = {}
|
||||||
|
|
||||||
# Extract the scale from the world matrix of the evaluated armature object.
|
# Extract the scale from the world matrix of the evaluated armature object.
|
||||||
for armature_object in options.armature_objects:
|
for armature_object in export_sequence.armature_objects:
|
||||||
evaluated_armature_object = armature_object.evaluated_get(context.evaluated_depsgraph_get())
|
evaluated_armature_object = armature_object.evaluated_get(context.evaluated_depsgraph_get())
|
||||||
_, _, scale = evaluated_armature_object.matrix_world.decompose()
|
_, _, scale = evaluated_armature_object.matrix_world.decompose()
|
||||||
scale *= options.scale
|
scale *= options.scale
|
||||||
@@ -223,6 +222,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
# locations.
|
# locations.
|
||||||
export_bones: List[PsaExportBone] = []
|
export_bones: List[PsaExportBone] = []
|
||||||
|
|
||||||
|
# TODO: we need different behavior here if it's ACTIVE_ACTION
|
||||||
for psx_bone, armature_object in psx_bone_create_result.bones:
|
for psx_bone, armature_object in psx_bone_create_result.bones:
|
||||||
if armature_object is None:
|
if armature_object is None:
|
||||||
export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0))))
|
export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0))))
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
||||||
if len(action_item.action.fcurves) == 0:
|
if len(action_item.action.fcurves) == 0:
|
||||||
continue
|
continue
|
||||||
export_sequence = PsaBuildSequence(context.active_object, animation_data)
|
export_sequence = PsaBuildSequence(self.armature_objects, animation_data)
|
||||||
export_sequence.name = action_item.name
|
export_sequence.name = action_item.name
|
||||||
export_sequence.nla_state.action = action_item.action
|
export_sequence.nla_state.action = action_item.action
|
||||||
export_sequence.nla_state.frame_start = action_item.frame_start
|
export_sequence.nla_state.frame_start = action_item.frame_start
|
||||||
@@ -483,7 +483,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
case 'TIMELINE_MARKERS':
|
case 'TIMELINE_MARKERS':
|
||||||
for marker_item in filter(lambda x: x.is_selected, pg.marker_list):
|
for marker_item in filter(lambda x: x.is_selected, pg.marker_list):
|
||||||
export_sequence = PsaBuildSequence(context.active_object, animation_data)
|
export_sequence = PsaBuildSequence(self.armature_objects, animation_data)
|
||||||
export_sequence.name = marker_item.name
|
export_sequence.name = marker_item.name
|
||||||
export_sequence.nla_state.frame_start = marker_item.frame_start
|
export_sequence.nla_state.frame_start = marker_item.frame_start
|
||||||
export_sequence.nla_state.frame_end = marker_item.frame_end
|
export_sequence.nla_state.frame_end = marker_item.frame_end
|
||||||
@@ -494,7 +494,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
case 'NLA_TRACK_STRIPS':
|
case 'NLA_TRACK_STRIPS':
|
||||||
for nla_strip_item in filter(lambda x: x.is_selected, pg.nla_strip_list):
|
for nla_strip_item in filter(lambda x: x.is_selected, pg.nla_strip_list):
|
||||||
export_sequence = PsaBuildSequence(context.active_object, animation_data)
|
export_sequence = PsaBuildSequence(self.armature_objects, animation_data)
|
||||||
export_sequence.name = nla_strip_item.name
|
export_sequence.name = nla_strip_item.name
|
||||||
export_sequence.nla_state.frame_start = nla_strip_item.frame_start
|
export_sequence.nla_state.frame_start = nla_strip_item.frame_start
|
||||||
export_sequence.nla_state.frame_end = nla_strip_item.frame_end
|
export_sequence.nla_state.frame_end = nla_strip_item.frame_end
|
||||||
@@ -504,7 +504,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
case 'ACTIVE_ACTION':
|
case 'ACTIVE_ACTION':
|
||||||
for active_action_item in filter(lambda x: x.is_selected, pg.active_action_list):
|
for active_action_item in filter(lambda x: x.is_selected, pg.active_action_list):
|
||||||
export_sequence = PsaBuildSequence(active_action_item.armature_object, active_action_item.armature_object.animation_data)
|
export_sequence = PsaBuildSequence([active_action_item.armature_object], active_action_item.armature_object.animation_data)
|
||||||
action = active_action_item.action
|
action = active_action_item.action
|
||||||
export_sequence.name = action.name
|
export_sequence.name = action.name
|
||||||
export_sequence.nla_state.action = action
|
export_sequence.nla_state.action = action
|
||||||
@@ -522,8 +522,6 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
options = PsaBuildOptions()
|
options = PsaBuildOptions()
|
||||||
options.armature_objects = self.armature_objects
|
|
||||||
options.animation_data = animation_data
|
|
||||||
options.sequences = export_sequences
|
options.sequences = export_sequences
|
||||||
options.bone_filter_mode = pg.bone_filter_mode
|
options.bone_filter_mode = pg.bone_filter_mode
|
||||||
options.bone_collection_indices = [PsxBoneCollection(x.armature_object_name, x.armature_data_name, x.index) for x in pg.bone_collection_list if x.is_selected]
|
options.bone_collection_indices = [PsxBoneCollection(x.armature_object_name, x.armature_data_name, x.index) for x in pg.bone_collection_list if x.is_selected]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class PSA_UL_export_sequences(UIList):
|
|||||||
# Show the filtering options by default.
|
# Show the filtering options by default.
|
||||||
self.use_filter_show = True
|
self.use_filter_show = True
|
||||||
|
|
||||||
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_property, index, flt_flag):
|
||||||
item = typing_cast(PSA_PG_export_action_list_item, item)
|
item = typing_cast(PSA_PG_export_action_list_item, item)
|
||||||
|
|
||||||
is_pose_marker = hasattr(item, 'is_pose_marker') and item.is_pose_marker
|
is_pose_marker = hasattr(item, 'is_pose_marker') and item.is_pose_marker
|
||||||
@@ -44,7 +44,7 @@ class PSA_UL_export_sequences(UIList):
|
|||||||
subrow.prop(pg, 'sequence_filter_pose_marker', icon_only=True, icon='PMARKER')
|
subrow.prop(pg, 'sequence_filter_pose_marker', icon_only=True, icon='PMARKER')
|
||||||
subrow.prop(pg, 'sequence_filter_reversed', text='', icon='FRAME_PREV')
|
subrow.prop(pg, 'sequence_filter_reversed', text='', icon='FRAME_PREV')
|
||||||
|
|
||||||
def filter_items(self, context, data, prop):
|
def filter_items(self, context, data, property):
|
||||||
pg = getattr(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)
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
|||||||
if animation_data is None:
|
if animation_data is None:
|
||||||
animation_data = armature_object.animation_data_create()
|
animation_data = armature_object.animation_data_create()
|
||||||
for action in actions:
|
for action in actions:
|
||||||
nla_track = armature_object.animation_data.nla_tracks.new()
|
nla_track = animation_data.nla_tracks.new()
|
||||||
nla_track.name = action.name
|
nla_track.name = action.name
|
||||||
nla_track.mute = True
|
nla_track.mute = True
|
||||||
nla_track.strips.new(name=action.name, start=0, action=action)
|
nla_track.strips.new(name=action.name, start=0, action=action)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def _try_fix_cue4parse_issue_103(sequences) -> bool:
|
|||||||
# Manually set the frame_start_index for each sequence. This assumes that the sequences are in order with
|
# Manually set the frame_start_index for each sequence. This assumes that the sequences are in order with
|
||||||
# no shared frames between sequences (all exporters that I know of do this, so it's a safe assumption).
|
# no shared frames between sequences (all exporters that I know of do this, so it's a safe assumption).
|
||||||
frame_start_index = 0
|
frame_start_index = 0
|
||||||
for i, sequence in enumerate(sequences):
|
for sequence in sequences:
|
||||||
sequence.frame_start_index = frame_start_index
|
sequence.frame_start_index = frame_start_index
|
||||||
frame_start_index += sequence.frame_count
|
frame_start_index += sequence.frame_count
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from bpy.props import CollectionProperty, StringProperty
|
from typing import cast as typing_cast
|
||||||
from bpy.types import Context, FileHandler, Operator, OperatorFileListElement, UILayout
|
from bpy.props import CollectionProperty, StringProperty, FloatProperty, EnumProperty
|
||||||
|
from bpy.types import Armature, Context, FileHandler, Operator, OperatorFileListElement, UILayout
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
|
|
||||||
|
from ...shared.helpers import get_coordinate_system_transform
|
||||||
|
from ...shared.types import AxisMixin
|
||||||
|
|
||||||
from ..importer import PskImportOptions, import_psk
|
from ..importer import PskImportOptions, import_psk
|
||||||
from ..properties import PskImportMixin
|
from ..properties import PskImportMixin
|
||||||
from ..reader import read_psk
|
from ..reader import read_psk
|
||||||
@@ -162,6 +166,46 @@ class PSK_OT_import_drag_and_drop(Operator, PskImportMixin):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class PSK_OT_create_bones_from_selected_objects(Operator, AxisMixin):
|
||||||
|
bl_idname = 'psk.create_bones_from_selected_objects'
|
||||||
|
bl_label = 'Create Bones from Selected Objects'
|
||||||
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
|
length: FloatProperty(name='Length', subtype='DISTANCE', default=0.01)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: Context) -> bool:
|
||||||
|
return context.active_object is not None and context.active_object.type == 'ARMATURE'
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
assert context.window_manager
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def execute(self, context: Context):
|
||||||
|
armature_object = context.active_object
|
||||||
|
|
||||||
|
assert armature_object
|
||||||
|
|
||||||
|
armature_data = typing_cast(Armature, armature_object.data)
|
||||||
|
axis_transform = get_coordinate_system_transform(self.forward_axis, self.up_axis)
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
for index, obj in enumerate(context.selected_objects):
|
||||||
|
if obj == armature_object:
|
||||||
|
continue
|
||||||
|
edit_bone_matrix = armature_object.matrix_world.inverted() @ obj.matrix_world
|
||||||
|
edit_bone = armature_data.edit_bones.new(f'{obj.name}_{index}')
|
||||||
|
# translation, rotation, _ = edit_bone_matrix.decompose()
|
||||||
|
edit_bone.length = self.length
|
||||||
|
edit_bone.matrix = edit_bone_matrix @ axis_transform
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
# TODO: move to another file
|
# TODO: move to another file
|
||||||
class PSK_FH_import(FileHandler):
|
class PSK_FH_import(FileHandler):
|
||||||
bl_idname = 'PSK_FH_import'
|
bl_idname = 'PSK_FH_import'
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class PskImportResult:
|
|||||||
self.mesh_object: Optional[Object] = None
|
self.mesh_object: Optional[Object] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def root_object(self) -> Object:
|
def root_object(self) -> Optional[Object]:
|
||||||
return self.armature_object if self.armature_object is not None else self.mesh_object
|
return self.armature_object if self.armature_object is not None else self.mesh_object
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user