Compare commits
4 Commits
psa-collec
...
9.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0ef2f7ce2 | ||
|
|
c38773002d | ||
|
|
d178de893f | ||
|
|
a34570fc1a |
40
README.md
40
README.md
@@ -13,37 +13,43 @@ For Blender 4.2 and higher, download the latest version from the [Blender Extens
|
|||||||
For Blender 4.1 and lower, see [Legacy Compatibility](#legacy-compatibility).
|
For Blender 4.1 and lower, see [Legacy Compatibility](#legacy-compatibility).
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
* Full PSK/PSA import and export capabilities.
|
* [Bone collections](https://docs.blender.org/manual/en/latest/animation/armatures/bones/bone_collections.html#bone-collections) can be excluded from PSK/PSA export (useful for excluding non-contributing bones such as IK controllers).
|
||||||
* Non-standard file section data (.pskx) is supported for import only (vertex normals, extra UV channels, vertex colors, shape keys).
|
* [Collection Exporters](https://docs.blender.org/manual/en/latest/scene_layout/collections/collections.html#exporters) for reliable, repeatable export workflow.
|
||||||
|
* Non-standard model data (.pskx) is supported for import only (vertex normals, extra UV channels, vertex colors, shape keys).
|
||||||
|
* Manual re-ordering of material slots on export.
|
||||||
|
* Non-standard animation data is supported for import only (scale keys).
|
||||||
* Fine-grained PSA sequence importing for efficient workflow when working with large PSA files.
|
* Fine-grained PSA sequence importing for efficient workflow when working with large PSA files.
|
||||||
* PSA sequence metadata (e.g., frame rate) is preserved on import, allowing this data to be reused on export.
|
* PSA sequence metadata (e.g., frame rate) is preserved on import, allowing this data to be reused on export.
|
||||||
* [Bone collections](https://docs.blender.org/manual/en/latest/animation/armatures/bones/bone_collections.html#bone-collections) can be excluded from PSK/PSA export (useful for excluding non-contributing bones such as IK controllers).
|
|
||||||
* PSA sequences can be exported directly from actions or delineated using a scene's [timeline markers](https://docs.blender.org/manual/en/latest/animation/markers.html), pose markers, or NLA track strips, allowing direct use of the [NLA](https://docs.blender.org/manual/en/latest/editors/nla/index.html) when creating sequences.
|
* PSA sequences can be exported directly from actions or delineated using a scene's [timeline markers](https://docs.blender.org/manual/en/latest/animation/markers.html), pose markers, or NLA track strips, allowing direct use of the [NLA](https://docs.blender.org/manual/en/latest/editors/nla/index.html) when creating sequences.
|
||||||
* Manual re-ordering of material slots.
|
* Compress exported sequences via resampling ratios or frame quotas.
|
||||||
* Multiple armature objects can be exported to a single PSK or PSA file, allowing seamless use of [action slots](https://docs.blender.org/manual/en/latest/animation/actions.html#action-slots).
|
|
||||||
* Support for exporting instance collections.
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
## Exporting a PSK
|
|
||||||
1. Select the mesh objects you wish to export.
|
|
||||||
2. Navigate to `File` > `Export` > `Unreal PSK (.psk)`.
|
|
||||||
3. Enter the file name and click `Export`.
|
|
||||||
|
|
||||||
## Importing a PSK/PSKX
|
## Import
|
||||||
|
|
||||||
|
### Importing a PSK/PSKX
|
||||||
1. Navigate to `File` > `Import` > `Unreal PSK (.psk/.pskx)`.
|
1. Navigate to `File` > `Import` > `Unreal PSK (.psk/.pskx)`.
|
||||||
2. Select the PSK file you want to import and click `Import`.
|
2. Select the PSK file you want to import and click `Import`.
|
||||||
|
|
||||||
## Exporting a PSA
|
### Importing a PSA
|
||||||
1. Select the armature objects you wish to export.
|
|
||||||
2. Navigate to `File` > `Export` > `Unreal PSA (.psa)`.
|
|
||||||
3. Enter the file name and click `Export`.
|
|
||||||
|
|
||||||
## Importing a PSA
|
|
||||||
1. Select an armature that you want import animations for.
|
1. Select an armature that you want import animations for.
|
||||||
2. Navigate to `File` > `Import` > `Unreal PSA (.psa)`.
|
2. Navigate to `File` > `Import` > `Unreal PSA (.psa)`.
|
||||||
3. Select the PSA file you want to import.
|
3. Select the PSA file you want to import.
|
||||||
4. Select the sequences that you want to import and click `Import`.
|
4. Select the sequences that you want to import and click `Import`.
|
||||||
|
|
||||||
|
## Export
|
||||||
|
It is highly recommended to use the provided [Collection Exporters](https://docs.blender.org/manual/en/latest/scene_layout/collections/collections.html#exporters) workflow, since it allows for highly reliable, repeatable exports of both PSK and PSA files. However, the traditional export workflow is available as well.
|
||||||
|
|
||||||
|
### Exporting a PSK
|
||||||
|
1. Select the mesh objects you wish to export.
|
||||||
|
2. Navigate to `File` > `Export` > `Unreal PSK (.psk)`.
|
||||||
|
3. Enter the file name and click `Export`.
|
||||||
|
|
||||||
|
### Exporting a PSA
|
||||||
|
1. Select the armature objects you wish to export.
|
||||||
|
2. Navigate to `File` > `Export` > `Unreal PSA (.psa)`.
|
||||||
|
3. Enter the file name and click `Export`.
|
||||||
|
|
||||||
> Note that in order to see the imported actions applied to your armature, you must use the [Dope Sheet](https://docs.blender.org/manual/en/latest/editors/dope_sheet/introduction.html) or [Nonlinear Animation](https://docs.blender.org/manual/en/latest/editors/nla/introduction.html) editors.
|
> Note that in order to see the imported actions applied to your armature, you must use the [Dope Sheet](https://docs.blender.org/manual/en/latest/editors/dope_sheet/introduction.html) or [Nonlinear Animation](https://docs.blender.org/manual/en/latest/editors/nla/introduction.html) editors.
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class PsaBuildOptions:
|
|||||||
self.export_space = 'WORLD'
|
self.export_space = 'WORLD'
|
||||||
self.forward_axis = 'X'
|
self.forward_axis = 'X'
|
||||||
self.up_axis = 'Z'
|
self.up_axis = 'Z'
|
||||||
self.root_bone_name = 'ROOT'
|
|
||||||
self.sequence_source = 'ACTIONS' # One of ('ACTIONS', 'TIMELINE_MARKERS', 'NLA_STRIPS')
|
self.sequence_source = 'ACTIONS' # One of ('ACTIONS', 'TIMELINE_MARKERS', 'NLA_STRIPS')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -162,7 +161,6 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
psx_bone_create_result = create_psx_bones(
|
psx_bone_create_result = create_psx_bones(
|
||||||
armature_objects=armature_objects_for_bones,
|
armature_objects=armature_objects_for_bones,
|
||||||
export_space=options.export_space,
|
export_space=options.export_space,
|
||||||
root_bone_name=options.root_bone_name,
|
|
||||||
forward_axis=options.forward_axis,
|
forward_axis=options.forward_axis,
|
||||||
up_axis=options.up_axis,
|
up_axis=options.up_axis,
|
||||||
scale=options.scale,
|
scale=options.scale,
|
||||||
|
|||||||
@@ -346,14 +346,6 @@ class PSA_OT_export_collection(Operator, ExportHelper, PsaExportMixin):
|
|||||||
op = col.operator(PSK_OT_bone_collection_list_select_all.bl_idname, text='', icon='CHECKBOX_DEHLT')
|
op = col.operator(PSK_OT_bone_collection_list_select_all.bl_idname, text='', icon='CHECKBOX_DEHLT')
|
||||||
op.is_selected = False
|
op.is_selected = False
|
||||||
|
|
||||||
advanced_bones_header, advanced_bones_panel = bones_panel.panel('Advanced', default_closed=True)
|
|
||||||
advanced_bones_header.label(text='Advanced')
|
|
||||||
if advanced_bones_panel:
|
|
||||||
flow = advanced_bones_panel.grid_flow(row_major=True)
|
|
||||||
flow.use_property_split = True
|
|
||||||
flow.use_property_decorate = False
|
|
||||||
flow.prop(self, 'root_bone_name')
|
|
||||||
|
|
||||||
# Transform
|
# Transform
|
||||||
transform_header, transform_panel = layout.panel('Transform', default_closed=False)
|
transform_header, transform_panel = layout.panel('Transform', default_closed=False)
|
||||||
transform_header.label(text='Transform', icon='DRIVER_TRANSFORM')
|
transform_header.label(text='Transform', icon='DRIVER_TRANSFORM')
|
||||||
@@ -565,7 +557,6 @@ def create_psa_export_options(context: Context, armature_objects: Sequence[Objec
|
|||||||
options.scale = pg.scale
|
options.scale = pg.scale
|
||||||
options.forward_axis = pg.forward_axis
|
options.forward_axis = pg.forward_axis
|
||||||
options.up_axis = pg.up_axis
|
options.up_axis = pg.up_axis
|
||||||
options.root_bone_name = pg.root_bone_name
|
|
||||||
options.sequence_source = pg.sequence_source
|
options.sequence_source = pg.sequence_source
|
||||||
|
|
||||||
return options
|
return options
|
||||||
@@ -626,14 +617,6 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
rows=rows
|
rows=rows
|
||||||
)
|
)
|
||||||
|
|
||||||
bones_advanced_header, bones_advanced_panel = bones_panel.panel('Bones Advanced', default_closed=True)
|
|
||||||
bones_advanced_header.label(text='Advanced')
|
|
||||||
if bones_advanced_panel:
|
|
||||||
flow = bones_advanced_panel.grid_flow()
|
|
||||||
flow.use_property_split = True
|
|
||||||
flow.use_property_decorate = False
|
|
||||||
flow.prop(pg, 'root_bone_name', text='Root Bone Name')
|
|
||||||
|
|
||||||
# TRANSFORM
|
# TRANSFORM
|
||||||
transform_header, transform_panel = layout.panel('Advanced', default_closed=False)
|
transform_header, transform_panel = layout.panel('Advanced', default_closed=False)
|
||||||
transform_header.label(text='Transform', icon='DRIVER_TRANSFORM')
|
transform_header.label(text='Transform', icon='DRIVER_TRANSFORM')
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class PskBuildOptions(object):
|
|||||||
self.export_space = 'WORLD'
|
self.export_space = 'WORLD'
|
||||||
self.forward_axis = 'X'
|
self.forward_axis = 'X'
|
||||||
self.up_axis = 'Z'
|
self.up_axis = 'Z'
|
||||||
self.root_bone_name = 'ROOT'
|
|
||||||
|
|
||||||
|
|
||||||
class PskBuildResult(object):
|
class PskBuildResult(object):
|
||||||
@@ -99,7 +98,6 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
|
|||||||
forward_axis=options.forward_axis,
|
forward_axis=options.forward_axis,
|
||||||
up_axis=options.up_axis,
|
up_axis=options.up_axis,
|
||||||
scale=options.scale,
|
scale=options.scale,
|
||||||
root_bone_name=options.root_bone_name,
|
|
||||||
bone_filter_mode=options.bone_filter_mode,
|
bone_filter_mode=options.bone_filter_mode,
|
||||||
bone_collection_indices=options.bone_collection_indices
|
bone_collection_indices=options.bone_collection_indices
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ def get_psk_build_options_from_property_group(scene: Scene, pg: PskExportMixin)
|
|||||||
options.export_space = pg.export_space
|
options.export_space = pg.export_space
|
||||||
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]
|
||||||
options.root_bone_name = pg.root_bone_name
|
|
||||||
options.material_order_mode = pg.material_order_mode
|
options.material_order_mode = pg.material_order_mode
|
||||||
options.material_name_list = [x.material_name for x in pg.material_name_list]
|
options.material_name_list = [x.material_name for x in pg.material_name_list]
|
||||||
|
|
||||||
@@ -309,14 +308,6 @@ class PSK_OT_export_collection(Operator, ExportHelper, PskExportMixin):
|
|||||||
op = col.operator(PSK_OT_bone_collection_list_select_all.bl_idname, text='', icon='CHECKBOX_DEHLT')
|
op = col.operator(PSK_OT_bone_collection_list_select_all.bl_idname, text='', icon='CHECKBOX_DEHLT')
|
||||||
op.is_selected = False
|
op.is_selected = False
|
||||||
|
|
||||||
advanced_bones_header, advanced_bones_panel = bones_panel.panel('Advanced', default_closed=True)
|
|
||||||
advanced_bones_header.label(text='Advanced')
|
|
||||||
if advanced_bones_panel:
|
|
||||||
flow = advanced_bones_panel.grid_flow(row_major=True)
|
|
||||||
flow.use_property_split = True
|
|
||||||
flow.use_property_decorate = False
|
|
||||||
flow.prop(self, 'root_bone_name')
|
|
||||||
|
|
||||||
# Materials
|
# Materials
|
||||||
materials_header, materials_panel = layout.panel('Materials', default_closed=False)
|
materials_header, materials_panel = layout.panel('Materials', default_closed=False)
|
||||||
materials_header.label(text='Materials', icon='MATERIAL')
|
materials_header.label(text='Materials', icon='MATERIAL')
|
||||||
@@ -429,13 +420,6 @@ class PSK_OT_export(Operator, ExportHelper):
|
|||||||
row = bones_panel.row()
|
row = bones_panel.row()
|
||||||
rows = max(3, min(len(pg.bone_collection_list), 10))
|
rows = max(3, min(len(pg.bone_collection_list), 10))
|
||||||
row.template_list('PSX_UL_bone_collection_list', '', pg, 'bone_collection_list', pg, 'bone_collection_list_index', rows=rows)
|
row.template_list('PSX_UL_bone_collection_list', '', pg, 'bone_collection_list', pg, 'bone_collection_list_index', rows=rows)
|
||||||
bones_advanced_header, bones_advanced_panel = bones_panel.panel('Advanced', default_closed=True)
|
|
||||||
bones_advanced_header.label(text='Advanced')
|
|
||||||
if bones_advanced_panel:
|
|
||||||
flow = bones_advanced_panel.grid_flow(row_major=True)
|
|
||||||
flow.use_property_split = True
|
|
||||||
flow.use_property_decorate = False
|
|
||||||
flow.prop(pg, 'root_bone_name')
|
|
||||||
|
|
||||||
# Materials
|
# Materials
|
||||||
materials_header, materials_panel = layout.panel('Materials', default_closed=False)
|
materials_header, materials_panel = layout.panel('Materials', default_closed=False)
|
||||||
|
|||||||
@@ -332,7 +332,6 @@ class ObjectTree:
|
|||||||
def create_psx_bones(
|
def create_psx_bones(
|
||||||
armature_objects: list[Object],
|
armature_objects: list[Object],
|
||||||
export_space: str = 'WORLD',
|
export_space: str = 'WORLD',
|
||||||
root_bone_name: str = 'ROOT',
|
|
||||||
forward_axis: str = 'X',
|
forward_axis: str = 'X',
|
||||||
up_axis: str = 'Z',
|
up_axis: str = 'Z',
|
||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
@@ -517,13 +516,10 @@ def create_psx_bones(
|
|||||||
bone_name_counts = Counter(bone.psx_bone.name.decode('windows-1252').upper() for bone in bones)
|
bone_name_counts = Counter(bone.psx_bone.name.decode('windows-1252').upper() for bone in bones)
|
||||||
for bone_name, count in bone_name_counts.items():
|
for bone_name, count in bone_name_counts.items():
|
||||||
if count > 1:
|
if count > 1:
|
||||||
error_message = f'Found {count} bones with the name "{bone_name}". '
|
raise RuntimeError(
|
||||||
f'Bone names must be unique when compared case-insensitively.'
|
f'Found {count} bones with the name "{bone_name}". '
|
||||||
|
f'Bone names must be unique when compared case-insensitively.'
|
||||||
if len(armature_objects) > 1 and bone_name == root_bone_name.upper():
|
)
|
||||||
error_message += f' This is the name of the automatically generated root bone. Consider changing this '
|
|
||||||
f''
|
|
||||||
raise RuntimeError(error_message)
|
|
||||||
|
|
||||||
# Apply the scale to the bone locations.
|
# Apply the scale to the bone locations.
|
||||||
for bone in bones:
|
for bone in bones:
|
||||||
|
|||||||
@@ -179,11 +179,6 @@ class PsxBoneExportMixin:
|
|||||||
)
|
)
|
||||||
bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item)
|
bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item)
|
||||||
bone_collection_list_index: IntProperty(default=0, name='', description='')
|
bone_collection_list_index: IntProperty(default=0, name='', description='')
|
||||||
root_bone_name: StringProperty(
|
|
||||||
name='Root Bone Name',
|
|
||||||
description='The name of the root bone when exporting a PSK with either no armature or multiple armatures',
|
|
||||||
default='ROOT',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PSX_PG_scene_export(PropertyGroup, TransformMixin):
|
class PSX_PG_scene_export(PropertyGroup, TransformMixin):
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ class PsxBoneExportMixin:
|
|||||||
bone_filter_mode: str
|
bone_filter_mode: str
|
||||||
bone_collection_list: BpyCollectionProperty[PSX_PG_bone_collection_list_item]
|
bone_collection_list: BpyCollectionProperty[PSX_PG_bone_collection_list_item]
|
||||||
bone_collection_list_index: int
|
bone_collection_list_index: int
|
||||||
root_bone_name: str
|
|
||||||
|
|
||||||
|
|
||||||
class PSX_PG_scene_export(TransformSourceMixin):
|
class PSX_PG_scene_export(TransformSourceMixin):
|
||||||
|
|||||||
BIN
io_scene_psk_psa/wheels/psk_psa_py-0.0.4-py3-none-any.whl
Normal file
BIN
io_scene_psk_psa/wheels/psk_psa_py-0.0.4-py3-none-any.whl
Normal file
Binary file not shown.
Reference in New Issue
Block a user