Compare commits
6 Commits
8.2.1
...
multi-arma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e704069763 | ||
|
|
8ed985263c | ||
|
|
d91408ecab | ||
|
|
dd1ea683bb | ||
|
|
240b79d374 | ||
|
|
33e7862288 |
@@ -1,6 +1,41 @@
|
|||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
|
|
||||||
if 'bpy' in locals():
|
from .shared import data as shared_data, types as shared_types, helpers as shared_helpers
|
||||||
|
from .shared import dfs as shared_dfs, ui as shared_ui
|
||||||
|
from .psk import (
|
||||||
|
builder as psk_builder,
|
||||||
|
data as psk_data,
|
||||||
|
importer as psk_importer,
|
||||||
|
properties as psk_properties,
|
||||||
|
writer as psk_writer,
|
||||||
|
)
|
||||||
|
from .psk import reader as psk_reader, ui as psk_ui
|
||||||
|
from .psk.export import (
|
||||||
|
operators as psk_export_operators,
|
||||||
|
properties as psk_export_properties,
|
||||||
|
ui as psk_export_ui,
|
||||||
|
)
|
||||||
|
from .psk.import_ import operators as psk_import_operators
|
||||||
|
|
||||||
|
from .psa import (
|
||||||
|
config as psa_config,
|
||||||
|
data as psa_data,
|
||||||
|
writer as psa_writer,
|
||||||
|
reader as psa_reader,
|
||||||
|
builder as psa_builder,
|
||||||
|
importer as psa_importer,
|
||||||
|
)
|
||||||
|
from .psa.export import (
|
||||||
|
properties as psa_export_properties,
|
||||||
|
ui as psa_export_ui,
|
||||||
|
operators as psa_export_operators,
|
||||||
|
)
|
||||||
|
from .psa.import_ import operators as psa_import_operators
|
||||||
|
from .psa.import_ import ui as psa_import_ui, properties as psa_import_properties
|
||||||
|
|
||||||
|
_needs_reload = 'bpy' in locals()
|
||||||
|
|
||||||
|
if _needs_reload:
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
importlib.reload(shared_data)
|
importlib.reload(shared_data)
|
||||||
@@ -33,58 +68,10 @@ if 'bpy' in locals():
|
|||||||
importlib.reload(psa_import_properties)
|
importlib.reload(psa_import_properties)
|
||||||
importlib.reload(psa_import_operators)
|
importlib.reload(psa_import_operators)
|
||||||
importlib.reload(psa_import_ui)
|
importlib.reload(psa_import_ui)
|
||||||
else:
|
|
||||||
from .shared import data as shared_data, types as shared_types, helpers as shared_helpers
|
|
||||||
from .shared import dfs as shared_dfs, ui as shared_ui
|
|
||||||
from .psk import (
|
|
||||||
builder as psk_builder,
|
|
||||||
data as psk_data,
|
|
||||||
importer as psk_importer,
|
|
||||||
properties as psk_properties,
|
|
||||||
writer as psk_writer,
|
|
||||||
)
|
|
||||||
from .psk import reader as psk_reader, ui as psk_ui
|
|
||||||
from .psk.export import (
|
|
||||||
operators as psk_export_operators,
|
|
||||||
properties as psk_export_properties,
|
|
||||||
ui as psk_export_ui,
|
|
||||||
)
|
|
||||||
from .psk.import_ import operators as psk_import_operators
|
|
||||||
|
|
||||||
from .psa import (
|
|
||||||
config as psa_config,
|
|
||||||
data as psa_data,
|
|
||||||
writer as psa_writer,
|
|
||||||
reader as psa_reader,
|
|
||||||
builder as psa_builder,
|
|
||||||
importer as psa_importer,
|
|
||||||
)
|
|
||||||
from .psa.export import (
|
|
||||||
properties as psa_export_properties,
|
|
||||||
ui as psa_export_ui,
|
|
||||||
operators as psa_export_operators,
|
|
||||||
)
|
|
||||||
from .psa.import_ import operators as psa_import_operators
|
|
||||||
from .psa.import_ import ui as psa_import_ui, properties as psa_import_properties
|
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import PointerProperty
|
from bpy.props import PointerProperty
|
||||||
|
|
||||||
classes = shared_types.classes + \
|
|
||||||
shared_ui.classes + \
|
|
||||||
psk_properties.classes + \
|
|
||||||
psk_ui.classes + \
|
|
||||||
psk_import_operators.classes + \
|
|
||||||
psk_export_properties.classes + \
|
|
||||||
psk_export_operators.classes + \
|
|
||||||
psk_export_ui.classes + \
|
|
||||||
psa_export_properties.classes + \
|
|
||||||
psa_export_operators.classes + \
|
|
||||||
psa_export_ui.classes + \
|
|
||||||
psa_import_properties.classes + \
|
|
||||||
psa_import_operators.classes + \
|
|
||||||
psa_import_ui.classes
|
|
||||||
|
|
||||||
|
|
||||||
def psk_export_menu_func(self, context):
|
def psk_export_menu_func(self, context):
|
||||||
self.layout.operator(psk_export_operators.PSK_OT_export.bl_idname, text='Unreal PSK (.psk)')
|
self.layout.operator(psk_export_operators.PSK_OT_export.bl_idname, text='Unreal PSK (.psk)')
|
||||||
@@ -102,9 +89,26 @@ def psa_import_menu_func(self, context):
|
|||||||
self.layout.operator(psa_import_operators.PSA_OT_import.bl_idname, text='Unreal PSA (.psa)')
|
self.layout.operator(psa_import_operators.PSA_OT_import.bl_idname, text='Unreal PSA (.psa)')
|
||||||
|
|
||||||
|
|
||||||
|
_modules = (
|
||||||
|
shared_types,
|
||||||
|
shared_ui,
|
||||||
|
psk_properties,
|
||||||
|
psk_ui,
|
||||||
|
psk_import_operators,
|
||||||
|
psk_export_properties,
|
||||||
|
psk_export_operators,
|
||||||
|
psk_export_ui,
|
||||||
|
psa_export_properties,
|
||||||
|
psa_export_operators,
|
||||||
|
psa_export_ui,
|
||||||
|
psa_import_properties,
|
||||||
|
psa_import_operators,
|
||||||
|
psa_import_ui
|
||||||
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
for cls in classes:
|
for module in _modules:
|
||||||
bpy.utils.register_class(cls)
|
module.register()
|
||||||
bpy.types.TOPBAR_MT_file_export.append(psk_export_menu_func)
|
bpy.types.TOPBAR_MT_file_export.append(psk_export_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_import.append(psk_import_menu_func)
|
bpy.types.TOPBAR_MT_file_import.append(psk_import_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_export.append(psa_export_menu_func)
|
bpy.types.TOPBAR_MT_file_export.append(psa_export_menu_func)
|
||||||
@@ -128,8 +132,8 @@ def unregister():
|
|||||||
bpy.types.TOPBAR_MT_file_import.remove(psk_import_menu_func)
|
bpy.types.TOPBAR_MT_file_import.remove(psk_import_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_export.remove(psa_export_menu_func)
|
bpy.types.TOPBAR_MT_file_export.remove(psa_export_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_import.remove(psa_import_menu_func)
|
bpy.types.TOPBAR_MT_file_import.remove(psa_import_menu_func)
|
||||||
for cls in reversed(classes):
|
for module in reversed(_modules):
|
||||||
bpy.utils.unregister_class(cls)
|
module.unregister()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
id = "io_scene_psk_psa"
|
id = "io_scene_psk_psa"
|
||||||
version = "8.2.1"
|
version = "8.2.2"
|
||||||
name = "Unreal PSK/PSA (.psk/.psa)"
|
name = "Unreal PSK/PSA (.psk/.psa)"
|
||||||
tagline = "Import and export PSK and PSA files used in Unreal Engine"
|
tagline = "Import and export PSK and PSA files used in Unreal Engine"
|
||||||
maintainer = "Colin Basnett <cmbasnett@gmail.com>"
|
maintainer = "Colin Basnett <cmbasnett@gmail.com>"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -103,8 +102,13 @@ def _get_pose_bone_location_and_rotation(
|
|||||||
|
|
||||||
|
|
||||||
def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
||||||
|
|
||||||
|
assert context.scene
|
||||||
|
assert context.window_manager
|
||||||
|
|
||||||
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.
|
||||||
@@ -139,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.
|
||||||
@@ -181,10 +185,9 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
frame = float(frame_start)
|
frame = float(frame_start)
|
||||||
|
|
||||||
# Link the action to the animation data and update view layer.
|
export_sequence.anim_data.action = export_sequence.nla_state.action
|
||||||
for armature_object in options.armature_objects:
|
|
||||||
armature_object.animation_data.action = export_sequence.nla_state.action
|
|
||||||
|
|
||||||
|
assert context.view_layer
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
|
|
||||||
def add_key(location: Vector, rotation: Quaternion):
|
def add_key(location: Vector, rotation: Quaternion):
|
||||||
@@ -208,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
|
||||||
@@ -219,11 +222,13 @@ 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))))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
assert armature_object.pose
|
||||||
pose_bone = armature_object.pose.bones[psx_bone.name.decode('windows-1252')]
|
pose_bone = armature_object.pose.bones[psx_bone.name.decode('windows-1252')]
|
||||||
|
|
||||||
export_bones.append(PsaExportBone(pose_bone, armature_object, armature_scales[armature_object]))
|
export_bones.append(PsaExportBone(pose_bone, armature_object, armature_scales[armature_object]))
|
||||||
@@ -321,6 +326,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
# Restore the previous actions & frame.
|
# Restore the previous actions & frame.
|
||||||
for armature_object, action in saved_armature_object_actions.items():
|
for armature_object, action in saved_armature_object_actions.items():
|
||||||
|
assert armature_object.animation_data
|
||||||
armature_object.animation_data.action = action
|
armature_object.animation_data.action = action
|
||||||
|
|
||||||
context.scene.frame_set(saved_frame_current)
|
context.scene.frame_set(saved_frame_current)
|
||||||
|
|||||||
@@ -462,6 +462,9 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
if animation_data is None:
|
if animation_data is None:
|
||||||
raise RuntimeError(f'No animation data for object \'{animation_data_object.name}\'')
|
raise RuntimeError(f'No animation data for object \'{animation_data_object.name}\'')
|
||||||
|
|
||||||
|
if context.active_object is None:
|
||||||
|
raise RuntimeError('No active object')
|
||||||
|
|
||||||
export_sequences: List[PsaBuildSequence] = []
|
export_sequences: List[PsaBuildSequence] = []
|
||||||
|
|
||||||
match pg.sequence_source:
|
match pg.sequence_source:
|
||||||
@@ -469,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
|
||||||
@@ -480,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
|
||||||
@@ -491,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
|
||||||
@@ -501,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
|
||||||
@@ -519,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]
|
||||||
@@ -658,10 +659,14 @@ class PSA_OT_export_bone_collections_deselect_all(Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSA_OT_export,
|
PSA_OT_export,
|
||||||
PSA_OT_export_actions_select_all,
|
PSA_OT_export_actions_select_all,
|
||||||
PSA_OT_export_actions_deselect_all,
|
PSA_OT_export_actions_deselect_all,
|
||||||
PSA_OT_export_bone_collections_select_all,
|
PSA_OT_export_bone_collections_select_all,
|
||||||
PSA_OT_export_bone_collections_deselect_all,
|
PSA_OT_export_bone_collections_deselect_all,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ sampling_mode_items = (
|
|||||||
|
|
||||||
def sequence_source_update_cb(self: 'PSA_PG_export', context: Context) -> None:
|
def sequence_source_update_cb(self: 'PSA_PG_export', context: Context) -> None:
|
||||||
armature_objects = []
|
armature_objects = []
|
||||||
|
assert context.view_layer
|
||||||
for dfs_object in dfs_view_layer_objects(context.view_layer):
|
for dfs_object in dfs_view_layer_objects(context.view_layer):
|
||||||
if dfs_object.obj.type == 'ARMATURE' and dfs_object.is_selected:
|
if dfs_object.obj.type == 'ARMATURE' and dfs_object.is_selected:
|
||||||
armature_objects.append(dfs_object.obj)
|
armature_objects.append(dfs_object.obj)
|
||||||
@@ -262,10 +263,14 @@ def filter_sequences(pg: PSA_PG_export, sequences) -> List[int]:
|
|||||||
return flt_flags
|
return flt_flags
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSA_PG_export_action_list_item,
|
PSA_PG_export_action_list_item,
|
||||||
PSA_PG_export_timeline_markers,
|
PSA_PG_export_timeline_markers,
|
||||||
PSA_PG_export_nla_strip_list_item,
|
PSA_PG_export_nla_strip_list_item,
|
||||||
PSA_PG_export_active_action_list_item,
|
PSA_PG_export_active_action_list_item,
|
||||||
PSA_PG_export,
|
PSA_PG_export,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -52,6 +52,10 @@ class PSA_UL_export_sequences(UIList):
|
|||||||
return flt_flags, flt_neworder
|
return flt_flags, flt_neworder
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSA_UL_export_sequences,
|
PSA_UL_export_sequences,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ from bpy_extras.io_utils import ImportHelper
|
|||||||
|
|
||||||
from .properties import PsaImportMixin, get_visible_sequences
|
from .properties import PsaImportMixin, get_visible_sequences
|
||||||
from ..config import read_psa_config
|
from ..config import read_psa_config
|
||||||
from ..importer import PsaImportOptions, import_psa
|
from ..importer import BoneMapping, PsaImportOptions, import_psa
|
||||||
from ..reader import PsaReader
|
from ..reader import PsaReader
|
||||||
|
|
||||||
|
|
||||||
def psa_import_poll(cls, context: Context):
|
def psa_import_poll(cls, context: Context):
|
||||||
|
assert context.view_layer and context.view_layer.objects.active
|
||||||
active_object = context.view_layer.objects.active
|
active_object = context.view_layer.objects.active
|
||||||
if active_object is None or active_object.type != 'ARMATURE':
|
if active_object is None or active_object.type != 'ARMATURE':
|
||||||
cls.poll_message_set('The active object must be an armature')
|
cls.poll_message_set('The active object must be an armature')
|
||||||
@@ -32,10 +33,12 @@ class PSA_OT_import_sequences_select_from_text(Operator):
|
|||||||
return len(pg.sequence_list) > 0
|
return len(pg.sequence_list) > 0
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
assert context.window_manager
|
||||||
return context.window_manager.invoke_props_dialog(self, width=256)
|
return context.window_manager.invoke_props_dialog(self, width=256)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
assert layout
|
||||||
pg = getattr(context.scene, 'psa_import')
|
pg = getattr(context.scene, 'psa_import')
|
||||||
layout.label(icon='INFO', text='Each sequence name should be on a new line.')
|
layout.label(icon='INFO', text='Each sequence name should be on a new line.')
|
||||||
layout.prop(pg, 'select_text', text='')
|
layout.prop(pg, 'select_text', text='')
|
||||||
@@ -134,6 +137,8 @@ class PSA_OT_import_drag_and_drop(Operator, PsaImportMixin):
|
|||||||
warnings = []
|
warnings = []
|
||||||
sequences_count = 0
|
sequences_count = 0
|
||||||
|
|
||||||
|
assert context.view_layer and context.view_layer.objects.active
|
||||||
|
|
||||||
for file in self.files:
|
for file in self.files:
|
||||||
psa_path = str(os.path.join(self.directory, file.name))
|
psa_path = str(os.path.join(self.directory, file.name))
|
||||||
psa_reader = PsaReader(psa_path)
|
psa_reader = PsaReader(psa_path)
|
||||||
@@ -157,12 +162,14 @@ class PSA_OT_import_drag_and_drop(Operator, PsaImportMixin):
|
|||||||
|
|
||||||
def invoke(self, context: Context, event):
|
def invoke(self, context: Context, event):
|
||||||
# Make sure the selected object is an obj.
|
# Make sure the selected object is an obj.
|
||||||
|
assert context.view_layer and context.view_layer.objects.active
|
||||||
active_object = context.view_layer.objects.active
|
active_object = context.view_layer.objects.active
|
||||||
if active_object is None or active_object.type != 'ARMATURE':
|
if active_object is None or active_object.type != 'ARMATURE':
|
||||||
self.report({'ERROR_INVALID_CONTEXT'}, 'The active object must be an armature')
|
self.report({'ERROR_INVALID_CONTEXT'}, 'The active object must be an armature')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# Show the import operator properties in a pop-up dialog (do not use the file selector).
|
# Show the import operator properties in a pop-up dialog (do not use the file selector).
|
||||||
|
assert context.window_manager
|
||||||
context.window_manager.invoke_props_dialog(self)
|
context.window_manager.invoke_props_dialog(self)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
@@ -181,7 +188,10 @@ def psa_import_options_from_property_group(pg: PsaImportMixin, sequence_names: I
|
|||||||
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
|
options.should_convert_to_samples = pg.should_convert_to_samples
|
||||||
options.bone_mapping_mode = pg.bone_mapping_mode
|
options.bone_mapping = BoneMapping(
|
||||||
|
is_case_sensitive=pg.bone_mapping_is_case_sensitive,
|
||||||
|
should_ignore_trailing_whitespace=pg.bone_mapping_should_ignore_trailing_whitespace
|
||||||
|
)
|
||||||
options.fps_source = pg.fps_source
|
options.fps_source = pg.fps_source
|
||||||
options.fps_custom = pg.fps_custom
|
options.fps_custom = pg.fps_custom
|
||||||
options.translation_scale = pg.translation_scale
|
options.translation_scale = pg.translation_scale
|
||||||
@@ -236,7 +246,10 @@ class PSA_OT_import_all(Operator, PsaImportMixin):
|
|||||||
|
|
||||||
options = PsaImportOptions(
|
options = PsaImportOptions(
|
||||||
action_name_prefix=self.action_name_prefix,
|
action_name_prefix=self.action_name_prefix,
|
||||||
bone_mapping_mode=self.bone_mapping_mode,
|
bone_mapping=BoneMapping(
|
||||||
|
is_case_sensitive=self.bone_mapping_is_case_sensitive,
|
||||||
|
should_ignore_trailing_whitespace=self.bone_mapping_should_ignore_trailing_whitespace
|
||||||
|
),
|
||||||
fps_custom=self.fps_custom,
|
fps_custom=self.fps_custom,
|
||||||
fps_source=self.fps_source,
|
fps_source=self.fps_source,
|
||||||
sequence_names=sequence_names,
|
sequence_names=sequence_names,
|
||||||
@@ -250,6 +263,8 @@ class PSA_OT_import_all(Operator, PsaImportMixin):
|
|||||||
translation_scale=self.translation_scale
|
translation_scale=self.translation_scale
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert context.view_layer
|
||||||
|
assert context.view_layer.objects.active
|
||||||
result = _import_psa(context, options, self.filepath, context.view_layer.objects.active)
|
result = _import_psa(context, options, self.filepath, context.view_layer.objects.active)
|
||||||
|
|
||||||
if len(result.warnings) > 0:
|
if len(result.warnings) > 0:
|
||||||
@@ -308,12 +323,13 @@ class PSA_OT_import(Operator, ImportHelper, PsaImportMixin):
|
|||||||
def invoke(self, context: Context, event: Event):
|
def invoke(self, context: Context, event: Event):
|
||||||
# Attempt to load the PSA file for the pre-selected file.
|
# Attempt to load the PSA file for the pre-selected file.
|
||||||
load_psa_file(context, self.filepath)
|
load_psa_file(context, self.filepath)
|
||||||
|
assert context.window_manager
|
||||||
context.window_manager.fileselect_add(self)
|
context.window_manager.fileselect_add(self)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def draw(self, context: Context):
|
def draw(self, context: Context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
assert layout
|
||||||
pg = getattr(context.scene, 'psa_import')
|
pg = getattr(context.scene, 'psa_import')
|
||||||
|
|
||||||
sequences_header, sequences_panel = layout.panel('sequences_panel_id', default_closed=False)
|
sequences_header, sequences_panel = layout.panel('sequences_panel_id', default_closed=False)
|
||||||
@@ -370,10 +386,14 @@ class PSA_OT_import(Operator, ImportHelper, PsaImportMixin):
|
|||||||
advanced_header.label(text='Advanced')
|
advanced_header.label(text='Advanced')
|
||||||
|
|
||||||
if advanced_panel:
|
if advanced_panel:
|
||||||
col = advanced_panel.column()
|
bone_mapping_header, bone_mapping_panel = layout.panel('bone_mapping_id', default_closed=False)
|
||||||
col.use_property_split = True
|
bone_mapping_header.label(text='Bone Mapping')
|
||||||
col.use_property_decorate = False
|
if bone_mapping_panel:
|
||||||
col.prop(self, 'bone_mapping_mode')
|
col = bone_mapping_panel.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(self, 'bone_mapping_is_case_sensitive')
|
||||||
|
col.prop(self, 'bone_mapping_should_ignore_trailing_whitespace')
|
||||||
|
|
||||||
col = advanced_panel.column()
|
col = advanced_panel.column()
|
||||||
col.use_property_split = True
|
col.use_property_split = True
|
||||||
@@ -412,10 +432,15 @@ def draw_psa_import_options_no_panels(layout, pg: PsaImportMixin):
|
|||||||
col.use_property_decorate = False
|
col.use_property_decorate = False
|
||||||
col.prop(pg, 'should_convert_to_samples')
|
col.prop(pg, 'should_convert_to_samples')
|
||||||
|
|
||||||
|
col = layout.column(heading='Bone Mapping')
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(pg, 'bone_mapping_is_case_sensitive')
|
||||||
|
col.prop(pg, 'bone_mapping_should_ignore_trailing_whitespace')
|
||||||
|
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.use_property_split = True
|
col.use_property_split = True
|
||||||
col.use_property_decorate = False
|
col.use_property_decorate = False
|
||||||
col.prop(pg, 'bone_mapping_mode')
|
|
||||||
col.prop(pg, 'translation_scale')
|
col.prop(pg, 'translation_scale')
|
||||||
|
|
||||||
col = layout.column(heading='Options')
|
col = layout.column(heading='Options')
|
||||||
@@ -434,11 +459,11 @@ class PSA_FH_import(FileHandler): # TODO: rename and add handling for PSA expor
|
|||||||
bl_file_extensions = '.psa'
|
bl_file_extensions = '.psa'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll_drop(cls, context: Context):
|
def poll_drop(cls, context: Context) -> bool:
|
||||||
return context.area and context.area.type == 'VIEW_3D'
|
return context.area is not None and context.area.type == 'VIEW_3D'
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSA_OT_import_sequences_select_all,
|
PSA_OT_import_sequences_select_all,
|
||||||
PSA_OT_import_sequences_deselect_all,
|
PSA_OT_import_sequences_deselect_all,
|
||||||
PSA_OT_import_sequences_select_from_text,
|
PSA_OT_import_sequences_select_from_text,
|
||||||
@@ -447,3 +472,6 @@ classes = (
|
|||||||
PSA_OT_import_drag_and_drop,
|
PSA_OT_import_drag_and_drop,
|
||||||
PSA_FH_import,
|
PSA_FH_import,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|||||||
@@ -80,12 +80,13 @@ class PsaImportMixin:
|
|||||||
description='Convert keyframes to read-only samples. '
|
description='Convert keyframes to read-only samples. '
|
||||||
'Recommended if you do not plan on editing the actions directly'
|
'Recommended if you do not plan on editing the actions directly'
|
||||||
)
|
)
|
||||||
bone_mapping_mode: EnumProperty(
|
bone_mapping_is_case_sensitive: BoolProperty(
|
||||||
name='Bone Mapping',
|
default=False,
|
||||||
options=set(),
|
name='Case Sensitive'
|
||||||
description='The method by which bones from the incoming PSA file are mapped to the armature',
|
)
|
||||||
items=bone_mapping_items,
|
bone_mapping_should_ignore_trailing_whitespace: BoolProperty(
|
||||||
default='CASE_INSENSITIVE'
|
default=True,
|
||||||
|
name='Ignore Trailing Whitespace'
|
||||||
)
|
)
|
||||||
fps_source: EnumProperty(name='FPS Source', items=fps_source_items)
|
fps_source: EnumProperty(name='FPS Source', items=fps_source_items)
|
||||||
fps_custom: FloatProperty(
|
fps_custom: FloatProperty(
|
||||||
@@ -175,9 +176,12 @@ def get_visible_sequences(pg: PSA_PG_import, sequences) -> List[PSA_PG_import_ac
|
|||||||
return visible_sequences
|
return visible_sequences
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSA_PG_import_action_list_item,
|
PSA_PG_import_action_list_item,
|
||||||
PSA_PG_bone,
|
PSA_PG_bone,
|
||||||
PSA_PG_data,
|
PSA_PG_data,
|
||||||
PSA_PG_import,
|
PSA_PG_import,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|||||||
@@ -42,8 +42,11 @@ class PSA_UL_import_actions(PSA_UL_sequences_mixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSA_UL_sequences,
|
PSA_UL_sequences,
|
||||||
PSA_UL_import_sequences,
|
PSA_UL_import_sequences,
|
||||||
PSA_UL_import_actions,
|
PSA_UL_import_actions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from typing import Sequence, Iterable, List, Optional, cast as typing_cast
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import re
|
||||||
from bpy.types import Armature, Context, FCurve, Object, Bone, PoseBone
|
from bpy.types import Armature, Context, FCurve, Object, Bone, PoseBone
|
||||||
from mathutils import Vector, Quaternion
|
from mathutils import Vector, Quaternion
|
||||||
|
|
||||||
@@ -9,11 +10,22 @@ from .config import PsaConfig, REMOVE_TRACK_LOCATION, REMOVE_TRACK_ROTATION
|
|||||||
from .reader import PsaReader
|
from .reader import PsaReader
|
||||||
from ..shared.data import PsxBone
|
from ..shared.data import PsxBone
|
||||||
|
|
||||||
|
class BoneMapping:
|
||||||
|
def __init__(self,
|
||||||
|
is_case_sensitive: bool = False,
|
||||||
|
should_ignore_trailing_whitespace: bool = True
|
||||||
|
):
|
||||||
|
self.is_case_sensitive = is_case_sensitive
|
||||||
|
# Ancient PSK and PSA exporters would, for some reason, pad the bone names with spaces
|
||||||
|
# instead of just writing null bytes, probably because the programmers were lazy.
|
||||||
|
# By default, we will ignore trailing whitespace when doing comparisons.
|
||||||
|
self.should_ignore_trailing_whitespace = should_ignore_trailing_whitespace
|
||||||
|
|
||||||
|
|
||||||
class PsaImportOptions(object):
|
class PsaImportOptions(object):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
action_name_prefix: str = '',
|
action_name_prefix: str = '',
|
||||||
bone_mapping_mode: str = 'CASE_INSENSITIVE',
|
bone_mapping: BoneMapping = BoneMapping(),
|
||||||
fps_custom: float = 30.0,
|
fps_custom: float = 30.0,
|
||||||
fps_source: str = 'SEQUENCE',
|
fps_source: str = 'SEQUENCE',
|
||||||
psa_config: PsaConfig = PsaConfig(),
|
psa_config: PsaConfig = PsaConfig(),
|
||||||
@@ -28,7 +40,7 @@ class PsaImportOptions(object):
|
|||||||
translation_scale: float = 1.0
|
translation_scale: float = 1.0
|
||||||
):
|
):
|
||||||
self.action_name_prefix = action_name_prefix
|
self.action_name_prefix = action_name_prefix
|
||||||
self.bone_mapping_mode = bone_mapping_mode
|
self.bone_mapping = bone_mapping
|
||||||
self.fps_custom = fps_custom
|
self.fps_custom = fps_custom
|
||||||
self.fps_source = fps_source
|
self.fps_source = fps_source
|
||||||
self.psa_config = psa_config
|
self.psa_config = psa_config
|
||||||
@@ -78,20 +90,30 @@ class PsaImportResult:
|
|||||||
self.warnings: List[str] = []
|
self.warnings: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
def _get_armature_bone_index_for_psa_bone(psa_bone_name: str, armature_bone_names: List[str], bone_mapping_mode: str = 'EXACT') -> Optional[int]:
|
def _get_armature_bone_index_for_psa_bone(psa_bone_name: str, armature_bone_names: List[str], bone_mapping: BoneMapping) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
@param psa_bone_name: The name of the PSA bone.
|
@param psa_bone_name: The name of the PSA bone.
|
||||||
@param armature_bone_names: The names of the bones in the armature.
|
@param armature_bone_names: The names of the bones in the armature.
|
||||||
@param bone_mapping_mode: One of `['EXACT', 'CASE_INSENSITIVE']`.
|
@param bone_mapping: Bone mapping information.
|
||||||
@return: The index of the armature bone that corresponds to the given PSA bone, or None if no such bone exists.
|
@return: The index of the armature bone that corresponds to the given PSA bone, or None if no such bone exists.
|
||||||
"""
|
"""
|
||||||
|
# Use regular expressions for bone name matching.
|
||||||
|
pattern = psa_bone_name
|
||||||
|
flags = 0
|
||||||
|
|
||||||
|
if bone_mapping.should_ignore_trailing_whitespace:
|
||||||
|
psa_bone_name = psa_bone_name.rstrip()
|
||||||
|
pattern += r'\s*'
|
||||||
|
|
||||||
|
if not bone_mapping.is_case_sensitive:
|
||||||
|
flags = re.IGNORECASE
|
||||||
|
|
||||||
|
pattern = re.compile(pattern, flags)
|
||||||
|
|
||||||
for armature_bone_index, armature_bone_name in enumerate(armature_bone_names):
|
for armature_bone_index, armature_bone_name in enumerate(armature_bone_names):
|
||||||
if bone_mapping_mode == 'CASE_INSENSITIVE':
|
if re.fullmatch(pattern, armature_bone_name):
|
||||||
if armature_bone_name.lower() == psa_bone_name.lower():
|
return armature_bone_index
|
||||||
return armature_bone_index
|
|
||||||
else:
|
|
||||||
if armature_bone_name == psa_bone_name:
|
|
||||||
return armature_bone_index
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -145,6 +167,9 @@ def _resample_sequence_data_matrix(sequence_data_matrix: np.ndarray, frame_step:
|
|||||||
|
|
||||||
|
|
||||||
def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object, options: PsaImportOptions) -> PsaImportResult:
|
def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object, options: PsaImportOptions) -> PsaImportResult:
|
||||||
|
|
||||||
|
assert context.window_manager
|
||||||
|
|
||||||
result = PsaImportResult()
|
result = PsaImportResult()
|
||||||
sequences = [psa_reader.sequences[x] for x in options.sequence_names]
|
sequences = [psa_reader.sequences[x] for x in options.sequence_names]
|
||||||
armature_data = typing_cast(Armature, armature_object.data)
|
armature_data = typing_cast(Armature, armature_object.data)
|
||||||
@@ -158,7 +183,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
|||||||
|
|
||||||
for psa_bone_index, psa_bone in enumerate(psa_reader.bones):
|
for psa_bone_index, psa_bone in enumerate(psa_reader.bones):
|
||||||
psa_bone_name: str = psa_bone.name.decode('windows-1252')
|
psa_bone_name: str = psa_bone.name.decode('windows-1252')
|
||||||
armature_bone_index = _get_armature_bone_index_for_psa_bone(psa_bone_name, armature_bone_names, options.bone_mapping_mode)
|
armature_bone_index = _get_armature_bone_index_for_psa_bone(psa_bone_name, armature_bone_names, options.bone_mapping)
|
||||||
if armature_bone_index is not None:
|
if armature_bone_index is not None:
|
||||||
# Ensure that no other PSA bone has been mapped to this armature bone yet.
|
# Ensure that no other PSA bone has been mapped to this armature bone yet.
|
||||||
if armature_bone_index not in armature_to_psa_bone_indices:
|
if armature_bone_index not in armature_to_psa_bone_indices:
|
||||||
@@ -259,6 +284,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
|||||||
case 'CUSTOM':
|
case 'CUSTOM':
|
||||||
target_fps = options.fps_custom
|
target_fps = options.fps_custom
|
||||||
case 'SCENE':
|
case 'SCENE':
|
||||||
|
assert context.scene
|
||||||
target_fps = context.scene.render.fps
|
target_fps = context.scene.render.fps
|
||||||
case 'SEQUENCE':
|
case 'SEQUENCE':
|
||||||
target_fps = sequence.fps
|
target_fps = sequence.fps
|
||||||
@@ -349,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,7 +1,7 @@
|
|||||||
import bmesh
|
import bmesh
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from bpy.types import Armature, Collection, Context, Depsgraph, Object, ArmatureModifier
|
from bpy.types import Armature, Collection, Context, Depsgraph, Object, ArmatureModifier, Mesh
|
||||||
from mathutils import Matrix
|
from mathutils import Matrix
|
||||||
from typing import Dict, Iterable, List, Optional, Set, cast as typing_cast
|
from typing import Dict, Iterable, List, Optional, Set, cast as typing_cast
|
||||||
from .data import Psk
|
from .data import Psk
|
||||||
@@ -94,9 +94,9 @@ def get_psk_input_objects_for_collection(collection: Collection) -> PskInputObje
|
|||||||
|
|
||||||
|
|
||||||
class PskBuildResult(object):
|
class PskBuildResult(object):
|
||||||
def __init__(self):
|
def __init__(self, psk: Psk, warnings: list[str]):
|
||||||
self.psk = None
|
self.psk: Psk = psk
|
||||||
self.warnings: List[str] = []
|
self.warnings: List[str] = warnings
|
||||||
|
|
||||||
|
|
||||||
def _get_mesh_export_space_matrix(armature_object: Optional[Object], export_space: str) -> Matrix:
|
def _get_mesh_export_space_matrix(armature_object: Optional[Object], export_space: str) -> Matrix:
|
||||||
@@ -137,9 +137,12 @@ def _get_material_name_indices(obj: Object, material_names: List[str]) -> Iterab
|
|||||||
|
|
||||||
|
|
||||||
def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuildOptions) -> PskBuildResult:
|
def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuildOptions) -> PskBuildResult:
|
||||||
|
|
||||||
|
assert context.window_manager
|
||||||
|
|
||||||
armature_objects = list(input_objects.armature_objects)
|
armature_objects = list(input_objects.armature_objects)
|
||||||
|
|
||||||
result = PskBuildResult()
|
warnings: list[str] = []
|
||||||
psk = Psk()
|
psk = Psk()
|
||||||
|
|
||||||
psx_bone_create_result = create_psx_bones(
|
psx_bone_create_result = create_psx_bones(
|
||||||
@@ -208,7 +211,8 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
|
|||||||
# Temporarily force the armature into the rest position.
|
# Temporarily force the armature into the rest position.
|
||||||
# We will undo this later.
|
# We will undo this later.
|
||||||
for armature_object in armature_objects:
|
for armature_object in armature_objects:
|
||||||
armature_object.data.pose_position = 'REST'
|
armature_data = typing_cast(Armature, armature_object.data)
|
||||||
|
armature_data.pose_position = 'REST'
|
||||||
|
|
||||||
material_names = [m.name if m is not None else 'None' for m in materials]
|
material_names = [m.name if m is not None else 'None' for m in materials]
|
||||||
|
|
||||||
@@ -232,7 +236,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
|
|||||||
match options.object_eval_state:
|
match options.object_eval_state:
|
||||||
case 'ORIGINAL':
|
case 'ORIGINAL':
|
||||||
mesh_object = obj
|
mesh_object = obj
|
||||||
mesh_data = obj.data
|
mesh_data = typing_cast(Mesh, obj.data)
|
||||||
case 'EVALUATED':
|
case 'EVALUATED':
|
||||||
# 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.
|
||||||
depsgraph = context.evaluated_depsgraph_get()
|
depsgraph = context.evaluated_depsgraph_get()
|
||||||
@@ -299,7 +303,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
|
|||||||
mesh_data.calc_loop_triangles()
|
mesh_data.calc_loop_triangles()
|
||||||
|
|
||||||
if mesh_data.uv_layers.active is None:
|
if mesh_data.uv_layers.active is None:
|
||||||
result.warnings.append(f'"{mesh_object.name}" has no active UV Map')
|
warnings.append(f'"{mesh_object.name}" has no active UV Map')
|
||||||
|
|
||||||
# Build a list of non-unique wedges.
|
# Build a list of non-unique wedges.
|
||||||
wedges = []
|
wedges = []
|
||||||
@@ -423,13 +427,12 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
|
|||||||
|
|
||||||
# Restore the original pose position of the armature objects.
|
# Restore the original pose position of the armature objects.
|
||||||
for armature_object, pose_position in original_armature_object_pose_positions.items():
|
for armature_object, pose_position in original_armature_object_pose_positions.items():
|
||||||
armature_object.data.pose_position = pose_position
|
armature_data = typing_cast(Armature, armature_object.data)
|
||||||
|
armature_data.pose_position = pose_position
|
||||||
|
|
||||||
# https://github.com/DarklightGames/io_scene_psk_psa/issues/129.
|
# https://github.com/DarklightGames/io_scene_psk_psa/issues/129.
|
||||||
psk.sort_and_normalize_weights()
|
psk.sort_and_normalize_weights()
|
||||||
|
|
||||||
context.window_manager.progress_end()
|
context.window_manager.progress_end()
|
||||||
|
|
||||||
result.psk = psk
|
return PskBuildResult(psk, warnings)
|
||||||
|
|
||||||
return result
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Iterable, List, Optional, cast as typing_cast
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import BoolProperty, StringProperty
|
from bpy.props import BoolProperty, StringProperty
|
||||||
from bpy.types import Collection, Context, Depsgraph, Material, Object, Operator, SpaceProperties, Scene
|
from bpy.types import Context, Depsgraph, Material, Object, Operator, Scene
|
||||||
from bpy_extras.io_utils import ExportHelper
|
from bpy_extras.io_utils import ExportHelper
|
||||||
|
|
||||||
from .properties import PskExportMixin
|
from .properties import PskExportMixin
|
||||||
@@ -91,6 +91,7 @@ class PSK_OT_populate_material_name_list(Operator):
|
|||||||
self.report({'ERROR_INVALID_CONTEXT'}, 'No valid export operator found in context')
|
self.report({'ERROR_INVALID_CONTEXT'}, 'No valid export operator found in context')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
depsgraph = context.evaluated_depsgraph_get()
|
depsgraph = context.evaluated_depsgraph_get()
|
||||||
|
assert context.collection
|
||||||
input_objects = get_psk_input_objects_for_collection(context.collection)
|
input_objects = get_psk_input_objects_for_collection(context.collection)
|
||||||
try:
|
try:
|
||||||
populate_material_name_list(depsgraph, [x.obj for x in input_objects.mesh_dfs_objects], export_operator.material_name_list)
|
populate_material_name_list(depsgraph, [x.obj for x in input_objects.mesh_dfs_objects], export_operator.material_name_list)
|
||||||
@@ -115,6 +116,7 @@ class PSK_OT_material_list_name_add(Operator):
|
|||||||
name: StringProperty(search=material_list_names_search_cb, name='Material Name', default='None')
|
name: StringProperty(search=material_list_names_search_cb, name='Material Name', default='None')
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
assert context.window_manager
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@@ -266,7 +268,10 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin):
|
|||||||
collection: StringProperty(options={'HIDDEN'})
|
collection: StringProperty(options={'HIDDEN'})
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
collection = bpy.data.collections.get(self.collection)
|
collection = bpy.data.collections.get(self.collection, None)
|
||||||
|
|
||||||
|
if collection is not None:
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
input_objects = get_psk_input_objects_for_collection(collection)
|
input_objects = get_psk_input_objects_for_collection(collection)
|
||||||
@@ -295,6 +300,8 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin):
|
|||||||
def draw(self, context: Context):
|
def draw(self, context: Context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
|
assert layout is not None
|
||||||
|
|
||||||
flow = layout.grid_flow(row_major=True)
|
flow = layout.grid_flow(row_major=True)
|
||||||
flow.use_property_split = True
|
flow.use_property_split = True
|
||||||
flow.use_property_decorate = False
|
flow.use_property_decorate = False
|
||||||
@@ -376,6 +383,8 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin):
|
|||||||
flow.enabled = False
|
flow.enabled = False
|
||||||
case 'CUSTOM':
|
case 'CUSTOM':
|
||||||
transform_source = self
|
transform_source = self
|
||||||
|
case _:
|
||||||
|
assert False, f'Invalid transform source: {self.transform_source}'
|
||||||
|
|
||||||
flow.prop(transform_source, 'scale')
|
flow.prop(transform_source, 'scale')
|
||||||
flow.prop(transform_source, 'forward_axis')
|
flow.prop(transform_source, 'forward_axis')
|
||||||
@@ -414,6 +423,7 @@ class PSK_OT_export(Operator, ExportHelper):
|
|||||||
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
assert context.window_manager
|
||||||
context.window_manager.fileselect_add(self)
|
context.window_manager.fileselect_add(self)
|
||||||
|
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
@@ -421,6 +431,8 @@ class PSK_OT_export(Operator, ExportHelper):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
|
assert layout
|
||||||
|
|
||||||
pg = getattr(context.scene, 'psk_export')
|
pg = getattr(context.scene, 'psk_export')
|
||||||
|
|
||||||
# Mesh
|
# Mesh
|
||||||
@@ -490,6 +502,8 @@ class PSK_OT_export(Operator, ExportHelper):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = getattr(context.scene, 'psk_export')
|
pg = getattr(context.scene, 'psk_export')
|
||||||
|
|
||||||
|
assert context.scene
|
||||||
|
|
||||||
input_objects = get_psk_input_objects_for_context(context)
|
input_objects = get_psk_input_objects_for_context(context)
|
||||||
options = get_psk_build_options_from_property_group(context.scene, pg)
|
options = get_psk_build_options_from_property_group(context.scene, pg)
|
||||||
|
|
||||||
@@ -509,7 +523,7 @@ class PSK_OT_export(Operator, ExportHelper):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSK_OT_material_list_move_up,
|
PSK_OT_material_list_move_up,
|
||||||
PSK_OT_material_list_move_down,
|
PSK_OT_material_list_move_down,
|
||||||
PSK_OT_export,
|
PSK_OT_export,
|
||||||
@@ -521,3 +535,6 @@ classes = (
|
|||||||
PSK_OT_material_list_name_move_down,
|
PSK_OT_material_list_name_move_down,
|
||||||
PSK_OT_material_list_name_add,
|
PSK_OT_material_list_name_add,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class PskExportMixin(ExportSpaceMixin, TransformMixin, PsxBoneExportMixin):
|
|||||||
material_name_list: CollectionProperty(type=PSK_PG_material_name_list_item)
|
material_name_list: CollectionProperty(type=PSK_PG_material_name_list_item)
|
||||||
material_name_list_index: IntProperty(default=0)
|
material_name_list_index: IntProperty(default=0)
|
||||||
should_export_vertex_normals: BoolProperty(
|
should_export_vertex_normals: BoolProperty(
|
||||||
'Export Vertex Normals',
|
name='Export Vertex Normals',
|
||||||
default=False,
|
default=False,
|
||||||
description='Export VTXNORMS section.'
|
description='Export VTXNORMS section.'
|
||||||
)
|
)
|
||||||
@@ -67,8 +67,12 @@ class PSK_PG_export(PropertyGroup, PskExportMixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSK_PG_material_list_item,
|
PSK_PG_material_list_item,
|
||||||
PSK_PG_material_name_list_item,
|
PSK_PG_material_name_list_item,
|
||||||
PSK_PG_export,
|
PSK_PG_export,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ class PSK_UL_material_names(UIList):
|
|||||||
row.prop(item, 'material_name', text='', emboss=False, icon_value=icon_value, icon='BLANK1' if icon_value == 0 else 'NONE')
|
row.prop(item, 'material_name', text='', emboss=False, icon_value=icon_value, icon='BLANK1' if icon_value == 0 else 'NONE')
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSK_UL_material_names,
|
PSK_UL_material_names,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
def get_psk_import_options_from_properties(property_group: PskImportMixin):
|
def get_psk_import_options_from_properties(property_group: PskImportMixin):
|
||||||
options = PskImportOptions()
|
options = PskImportOptions()
|
||||||
options.should_import_mesh = property_group.should_import_mesh
|
options.should_import_mesh = property_group.should_import_mesh
|
||||||
@@ -109,6 +114,7 @@ class PSK_OT_import(Operator, ImportHelper, PskImportMixin):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
assert self.layout
|
||||||
psk_import_draw(self.layout, self)
|
psk_import_draw(self.layout, self)
|
||||||
|
|
||||||
|
|
||||||
@@ -122,13 +128,15 @@ class PSK_OT_import_drag_and_drop(Operator, PskImportMixin):
|
|||||||
files: CollectionProperty(type=OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'})
|
files: CollectionProperty(type=OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context) -> bool:
|
||||||
return context.area and context.area.type == 'VIEW_3D'
|
return context.area is not None and context.area.type == 'VIEW_3D'
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
assert self.layout
|
||||||
psk_import_draw(self.layout, self)
|
psk_import_draw(self.layout, self)
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
assert context.window_manager
|
||||||
context.window_manager.invoke_props_dialog(self)
|
context.window_manager.invoke_props_dialog(self)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
@@ -158,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'
|
||||||
@@ -167,12 +215,15 @@ class PSK_FH_import(FileHandler):
|
|||||||
bl_file_extensions = '.psk;.pskx'
|
bl_file_extensions = '.psk;.pskx'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll_drop(cls, context: Context):
|
def poll_drop(cls, context: Context) -> bool:
|
||||||
return context.area and context.area.type == 'VIEW_3D'
|
return context.area is not None and context.area.type == 'VIEW_3D'
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSK_OT_import,
|
PSK_OT_import,
|
||||||
PSK_OT_import_drag_and_drop,
|
PSK_OT_import_drag_and_drop,
|
||||||
PSK_FH_import,
|
PSK_FH_import,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
@@ -62,6 +62,9 @@ def import_psk(psk: Psk, context: Context, name: str, options: PskImportOptions)
|
|||||||
armature_object = None
|
armature_object = None
|
||||||
mesh_object = None
|
mesh_object = None
|
||||||
|
|
||||||
|
assert context.scene
|
||||||
|
assert bpy.context.view_layer
|
||||||
|
|
||||||
if options.should_import_armature:
|
if options.should_import_armature:
|
||||||
# Armature
|
# Armature
|
||||||
armature_data = bpy.data.armatures.new(name)
|
armature_data = bpy.data.armatures.new(name)
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ class PskImportMixin:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSX_PG_material,
|
PSX_PG_material,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class PSK_PT_material(Panel):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
assert layout is not None
|
||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
material = context.material
|
material = context.material
|
||||||
@@ -23,6 +24,10 @@ class PSK_PT_material(Panel):
|
|||||||
col.prop(material.psk, 'mesh_triangle_bit_flags', expand=True, text='Flags')
|
col.prop(material.psk, 'mesh_triangle_bit_flags', expand=True, text='Flags')
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSK_PT_material,
|
PSK_PT_material,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|
||||||
|
|||||||
@@ -154,10 +154,13 @@ class PSX_PG_scene_export(PropertyGroup, TransformMixin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSX_PG_scene_export,
|
PSX_PG_scene_export,
|
||||||
PSX_PG_action_export,
|
PSX_PG_action_export,
|
||||||
PSX_PG_bone_collection_list_item,
|
PSX_PG_bone_collection_list_item,
|
||||||
PSX_UL_bone_collection_list,
|
PSX_UL_bone_collection_list,
|
||||||
PSX_PT_action,
|
PSX_PT_action,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ class PSX_PT_scene(Panel):
|
|||||||
flow.prop(psx_export, 'up_axis')
|
flow.prop(psx_export, 'up_axis')
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
_classes = (
|
||||||
PSX_PT_scene,
|
PSX_PT_scene,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from bpy.utils import register_classes_factory
|
||||||
|
register, unregister = register_classes_factory(_classes)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user