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).
|
||||
|
||||
# Features
|
||||
* Full PSK/PSA import and export capabilities.
|
||||
* Non-standard file section data (.pskx) is supported for import only (vertex normals, extra UV channels, vertex colors, shape keys).
|
||||
* [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).
|
||||
* [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.
|
||||
* 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.
|
||||
* Manual re-ordering of material slots.
|
||||
* 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.
|
||||
* Compress exported sequences via resampling ratios or frame quotas.
|
||||
|
||||
# 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)`.
|
||||
2. Select the PSK file you want to import and click `Import`.
|
||||
|
||||
## 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`.
|
||||
|
||||
## Importing a PSA
|
||||
### Importing a PSA
|
||||
1. Select an armature that you want import animations for.
|
||||
2. Navigate to `File` > `Import` > `Unreal PSA (.psa)`.
|
||||
3. Select the PSA file you want to 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.
|
||||
|
||||
# FAQ
|
||||
|
||||
@@ -39,7 +39,6 @@ class PsaBuildOptions:
|
||||
self.export_space = 'WORLD'
|
||||
self.forward_axis = 'X'
|
||||
self.up_axis = 'Z'
|
||||
self.root_bone_name = 'ROOT'
|
||||
self.sequence_source = 'ACTIONS' # One of ('ACTIONS', 'TIMELINE_MARKERS', 'NLA_STRIPS')
|
||||
|
||||
@property
|
||||
@@ -162,7 +161,6 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
||||
psx_bone_create_result = create_psx_bones(
|
||||
armature_objects=armature_objects_for_bones,
|
||||
export_space=options.export_space,
|
||||
root_bone_name=options.root_bone_name,
|
||||
forward_axis=options.forward_axis,
|
||||
up_axis=options.up_axis,
|
||||
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.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_header, transform_panel = layout.panel('Transform', default_closed=False)
|
||||
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.forward_axis = pg.forward_axis
|
||||
options.up_axis = pg.up_axis
|
||||
options.root_bone_name = pg.root_bone_name
|
||||
options.sequence_source = pg.sequence_source
|
||||
|
||||
return options
|
||||
@@ -626,14 +617,6 @@ class PSA_OT_export(Operator, ExportHelper):
|
||||
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_header, transform_panel = layout.panel('Advanced', default_closed=False)
|
||||
transform_header.label(text='Transform', icon='DRIVER_TRANSFORM')
|
||||
|
||||
@@ -31,7 +31,6 @@ class PskBuildOptions(object):
|
||||
self.export_space = 'WORLD'
|
||||
self.forward_axis = 'X'
|
||||
self.up_axis = 'Z'
|
||||
self.root_bone_name = 'ROOT'
|
||||
|
||||
|
||||
class PskBuildResult(object):
|
||||
@@ -99,7 +98,6 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
|
||||
forward_axis=options.forward_axis,
|
||||
up_axis=options.up_axis,
|
||||
scale=options.scale,
|
||||
root_bone_name=options.root_bone_name,
|
||||
bone_filter_mode=options.bone_filter_mode,
|
||||
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.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.root_bone_name = pg.root_bone_name
|
||||
options.material_order_mode = pg.material_order_mode
|
||||
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.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_header, materials_panel = layout.panel('Materials', default_closed=False)
|
||||
materials_header.label(text='Materials', icon='MATERIAL')
|
||||
@@ -429,13 +420,6 @@ class PSK_OT_export(Operator, ExportHelper):
|
||||
row = bones_panel.row()
|
||||
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)
|
||||
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_header, materials_panel = layout.panel('Materials', default_closed=False)
|
||||
|
||||
@@ -332,7 +332,6 @@ class ObjectTree:
|
||||
def create_psx_bones(
|
||||
armature_objects: list[Object],
|
||||
export_space: str = 'WORLD',
|
||||
root_bone_name: str = 'ROOT',
|
||||
forward_axis: str = 'X',
|
||||
up_axis: str = 'Z',
|
||||
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)
|
||||
for bone_name, count in bone_name_counts.items():
|
||||
if count > 1:
|
||||
error_message = 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)
|
||||
raise RuntimeError(
|
||||
f'Found {count} bones with the name "{bone_name}". '
|
||||
f'Bone names must be unique when compared case-insensitively.'
|
||||
)
|
||||
|
||||
# Apply the scale to the bone locations.
|
||||
for bone in bones:
|
||||
|
||||
@@ -179,11 +179,6 @@ class PsxBoneExportMixin:
|
||||
)
|
||||
bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item)
|
||||
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):
|
||||
|
||||
@@ -54,7 +54,6 @@ class PsxBoneExportMixin:
|
||||
bone_filter_mode: str
|
||||
bone_collection_list: BpyCollectionProperty[PSX_PG_bone_collection_list_item]
|
||||
bone_collection_list_index: int
|
||||
root_bone_name: str
|
||||
|
||||
|
||||
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