Compare commits
5 Commits
multi-arma
...
8.2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37f7cc4d9f | ||
|
|
93083f09f8 | ||
|
|
75660f9dc1 | ||
|
|
5421ac5151 | ||
|
|
9dcbb74058 |
@@ -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.2"
|
version = "8.2.4"
|
||||||
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, Iterable
|
from typing import Dict, List, Optional, Tuple
|
||||||
from mathutils import Matrix, Quaternion, Vector
|
from mathutils import Matrix, Quaternion, Vector
|
||||||
|
|
||||||
from ..shared.helpers import PsxBoneCollection, create_psx_bones, get_coordinate_system_transform
|
from ..shared.helpers import 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_objects: Iterable[Object], anim_data: AnimData):
|
def __init__(self, armature_object: Object, anim_data: AnimData):
|
||||||
self.armature_objects = list(armature_objects)
|
self.armature_object = armature_object
|
||||||
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,9 +27,10 @@ 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[PsxBoneCollection] = []
|
self.bone_collection_indices: List[PsaBoneCollectionIndex] = []
|
||||||
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
|
||||||
@@ -57,7 +58,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 is not None and pose_bone.parent is not None:
|
elif 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
|
||||||
@@ -108,7 +109,6 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
psa = Psa()
|
psa = Psa()
|
||||||
|
|
||||||
# TODO: move this OUT!
|
|
||||||
armature_objects_for_bones = options.armature_objects
|
armature_objects_for_bones = options.armature_objects
|
||||||
if options.sequence_source == 'ACTIVE_ACTION' and len(options.armature_objects) >= 2:
|
if options.sequence_source == 'ACTIVE_ACTION' and len(options.armature_objects) >= 2:
|
||||||
# Make sure that the data-block for all the selected armature objects is the same.
|
# Make sure that the data-block for all the selected armature objects is the same.
|
||||||
@@ -143,7 +143,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
export_sequence.name = export_sequence.name.strip()
|
export_sequence.name = export_sequence.name.strip()
|
||||||
|
|
||||||
# Save each armature object's current action and frame so that we can restore the state once we are done.
|
# Save each armature object's current action and frame so that we can restore the state once we are done.
|
||||||
saved_armature_object_actions = {o: o.animation_data.action if o.animation_data else None for o in options.armature_objects}
|
saved_armature_object_actions = {o: o.animation_data.action for o in options.armature_objects}
|
||||||
saved_frame_current = context.scene.frame_current
|
saved_frame_current = context.scene.frame_current
|
||||||
|
|
||||||
# Now build the PSA sequences.
|
# Now build the PSA sequences.
|
||||||
@@ -184,10 +184,11 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
psa_sequence.key_reduction = 1.0
|
psa_sequence.key_reduction = 1.0
|
||||||
|
|
||||||
frame = float(frame_start)
|
frame = float(frame_start)
|
||||||
|
|
||||||
export_sequence.anim_data.action = export_sequence.nla_state.action
|
|
||||||
|
|
||||||
assert context.view_layer
|
# Link the action to the animation data and update view layer.
|
||||||
|
for armature_object in options.armature_objects:
|
||||||
|
armature_object.animation_data.action = export_sequence.nla_state.action
|
||||||
|
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
|
|
||||||
def add_key(location: Vector, rotation: Quaternion):
|
def add_key(location: Vector, rotation: Quaternion):
|
||||||
@@ -211,7 +212,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 export_sequence.armature_objects:
|
for armature_object in options.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
|
||||||
@@ -222,7 +223,6 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
# locations.
|
# locations.
|
||||||
export_bones: List[PsaExportBone] = []
|
export_bones: List[PsaExportBone] = []
|
||||||
|
|
||||||
# TODO: we need different behavior here if it's ACTIVE_ACTION
|
|
||||||
for psx_bone, armature_object in psx_bone_create_result.bones:
|
for psx_bone, armature_object in psx_bone_create_result.bones:
|
||||||
if armature_object is None:
|
if armature_object is None:
|
||||||
export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0))))
|
export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0))))
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
||||||
if len(action_item.action.fcurves) == 0:
|
if len(action_item.action.fcurves) == 0:
|
||||||
continue
|
continue
|
||||||
export_sequence = PsaBuildSequence(self.armature_objects, animation_data)
|
export_sequence = PsaBuildSequence(context.active_object, animation_data)
|
||||||
export_sequence.name = action_item.name
|
export_sequence.name = action_item.name
|
||||||
export_sequence.nla_state.action = action_item.action
|
export_sequence.nla_state.action = action_item.action
|
||||||
export_sequence.nla_state.frame_start = action_item.frame_start
|
export_sequence.nla_state.frame_start = action_item.frame_start
|
||||||
@@ -483,7 +483,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
case 'TIMELINE_MARKERS':
|
case 'TIMELINE_MARKERS':
|
||||||
for marker_item in filter(lambda x: x.is_selected, pg.marker_list):
|
for marker_item in filter(lambda x: x.is_selected, pg.marker_list):
|
||||||
export_sequence = PsaBuildSequence(self.armature_objects, animation_data)
|
export_sequence = PsaBuildSequence(context.active_object, animation_data)
|
||||||
export_sequence.name = marker_item.name
|
export_sequence.name = marker_item.name
|
||||||
export_sequence.nla_state.frame_start = marker_item.frame_start
|
export_sequence.nla_state.frame_start = marker_item.frame_start
|
||||||
export_sequence.nla_state.frame_end = marker_item.frame_end
|
export_sequence.nla_state.frame_end = marker_item.frame_end
|
||||||
@@ -494,7 +494,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
case 'NLA_TRACK_STRIPS':
|
case 'NLA_TRACK_STRIPS':
|
||||||
for nla_strip_item in filter(lambda x: x.is_selected, pg.nla_strip_list):
|
for nla_strip_item in filter(lambda x: x.is_selected, pg.nla_strip_list):
|
||||||
export_sequence = PsaBuildSequence(self.armature_objects, animation_data)
|
export_sequence = PsaBuildSequence(context.active_object, animation_data)
|
||||||
export_sequence.name = nla_strip_item.name
|
export_sequence.name = nla_strip_item.name
|
||||||
export_sequence.nla_state.frame_start = nla_strip_item.frame_start
|
export_sequence.nla_state.frame_start = nla_strip_item.frame_start
|
||||||
export_sequence.nla_state.frame_end = nla_strip_item.frame_end
|
export_sequence.nla_state.frame_end = nla_strip_item.frame_end
|
||||||
@@ -504,7 +504,7 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
case 'ACTIVE_ACTION':
|
case 'ACTIVE_ACTION':
|
||||||
for active_action_item in filter(lambda x: x.is_selected, pg.active_action_list):
|
for active_action_item in filter(lambda x: x.is_selected, pg.active_action_list):
|
||||||
export_sequence = PsaBuildSequence([active_action_item.armature_object], active_action_item.armature_object.animation_data)
|
export_sequence = PsaBuildSequence(active_action_item.armature_object, active_action_item.armature_object.animation_data)
|
||||||
action = active_action_item.action
|
action = active_action_item.action
|
||||||
export_sequence.name = action.name
|
export_sequence.name = action.name
|
||||||
export_sequence.nla_state.action = action
|
export_sequence.nla_state.action = action
|
||||||
@@ -522,6 +522,8 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
options = PsaBuildOptions()
|
options = PsaBuildOptions()
|
||||||
|
options.armature_objects = self.armature_objects
|
||||||
|
options.animation_data = animation_data
|
||||||
options.sequences = export_sequences
|
options.sequences = export_sequences
|
||||||
options.bone_filter_mode = pg.bone_filter_mode
|
options.bone_filter_mode = pg.bone_filter_mode
|
||||||
options.bone_collection_indices = [PsxBoneCollection(x.armature_object_name, x.armature_data_name, x.index) for x in pg.bone_collection_list if x.is_selected]
|
options.bone_collection_indices = [PsxBoneCollection(x.armature_object_name, x.armature_data_name, x.index) for x in pg.bone_collection_list if x.is_selected]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class PSA_UL_export_sequences(UIList):
|
|||||||
# Show the filtering options by default.
|
# Show the filtering options by default.
|
||||||
self.use_filter_show = True
|
self.use_filter_show = True
|
||||||
|
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index, flt_flag):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
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, property):
|
def filter_items(self, context, data, prop):
|
||||||
pg = getattr(context.scene, 'psa_export')
|
pg = getattr(context.scene, 'psa_export')
|
||||||
actions = getattr(data, prop)
|
actions = getattr(data, prop)
|
||||||
flt_flags = filter_sequences(pg, actions)
|
flt_flags = filter_sequences(pg, actions)
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
|||||||
if animation_data is None:
|
if animation_data is None:
|
||||||
animation_data = armature_object.animation_data_create()
|
animation_data = armature_object.animation_data_create()
|
||||||
for action in actions:
|
for action in actions:
|
||||||
nla_track = animation_data.nla_tracks.new()
|
nla_track = armature_object.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 sequence in sequences:
|
for i, sequence in enumerate(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
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ def get_psk_build_options_from_property_group(scene: Scene, pg: PskExportMixin)
|
|||||||
match pg.transform_source:
|
match pg.transform_source:
|
||||||
case 'SCENE':
|
case 'SCENE':
|
||||||
transform_source = getattr(scene, 'psx_export')
|
transform_source = getattr(scene, 'psx_export')
|
||||||
case 'SELF':
|
case 'CUSTOM':
|
||||||
transform_source = pg
|
transform_source = pg
|
||||||
case _:
|
case _:
|
||||||
assert False, f'Invalid transform source: {pg.transform_source}'
|
assert False, f'Invalid transform source: {pg.transform_source}'
|
||||||
@@ -486,9 +486,24 @@ class PSK_OT_export(Operator, ExportHelper):
|
|||||||
flow.use_property_split = True
|
flow.use_property_split = True
|
||||||
flow.use_property_decorate = False
|
flow.use_property_decorate = False
|
||||||
flow.prop(pg, 'export_space')
|
flow.prop(pg, 'export_space')
|
||||||
flow.prop(pg, 'scale')
|
flow.prop(pg, 'transform_source')
|
||||||
flow.prop(pg, 'forward_axis')
|
|
||||||
flow.prop(pg, 'up_axis')
|
flow = transform_panel.grid_flow(row_major=True)
|
||||||
|
flow.use_property_split = True
|
||||||
|
flow.use_property_decorate = False
|
||||||
|
|
||||||
|
match pg.transform_source:
|
||||||
|
case 'SCENE':
|
||||||
|
transform_source = getattr(context.scene, 'psx_export')
|
||||||
|
flow.enabled = False
|
||||||
|
case 'CUSTOM':
|
||||||
|
transform_source = pg
|
||||||
|
case _:
|
||||||
|
assert False, f'Invalid transform source: {pg.transform_source}'
|
||||||
|
|
||||||
|
flow.prop(transform_source, 'scale')
|
||||||
|
flow.prop(transform_source, 'forward_axis')
|
||||||
|
flow.prop(transform_source, 'up_axis')
|
||||||
|
|
||||||
# Extended Format
|
# Extended Format
|
||||||
extended_format_header, extended_format_panel = layout.panel('Extended Format', default_closed=False)
|
extended_format_header, extended_format_panel = layout.panel('Extended Format', default_closed=False)
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from typing import cast as typing_cast
|
from bpy.props import CollectionProperty, StringProperty
|
||||||
from bpy.props import CollectionProperty, StringProperty, FloatProperty, EnumProperty
|
from bpy.types import Context, FileHandler, Operator, OperatorFileListElement, UILayout
|
||||||
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
|
||||||
@@ -166,46 +162,6 @@ class PSK_OT_import_drag_and_drop(Operator, PskImportMixin):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class PSK_OT_create_bones_from_selected_objects(Operator, AxisMixin):
|
|
||||||
bl_idname = 'psk.create_bones_from_selected_objects'
|
|
||||||
bl_label = 'Create Bones from Selected Objects'
|
|
||||||
bl_options = {'UNDO'}
|
|
||||||
|
|
||||||
length: FloatProperty(name='Length', subtype='DISTANCE', default=0.01)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context: Context) -> bool:
|
|
||||||
return context.active_object is not None and context.active_object.type == 'ARMATURE'
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
assert context.window_manager
|
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
|
||||||
|
|
||||||
def execute(self, context: Context):
|
|
||||||
armature_object = context.active_object
|
|
||||||
|
|
||||||
assert armature_object
|
|
||||||
|
|
||||||
armature_data = typing_cast(Armature, armature_object.data)
|
|
||||||
axis_transform = get_coordinate_system_transform(self.forward_axis, self.up_axis)
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
|
||||||
|
|
||||||
for index, obj in enumerate(context.selected_objects):
|
|
||||||
if obj == armature_object:
|
|
||||||
continue
|
|
||||||
edit_bone_matrix = armature_object.matrix_world.inverted() @ obj.matrix_world
|
|
||||||
edit_bone = armature_data.edit_bones.new(f'{obj.name}_{index}')
|
|
||||||
# translation, rotation, _ = edit_bone_matrix.decompose()
|
|
||||||
edit_bone.length = self.length
|
|
||||||
edit_bone.matrix = edit_bone_matrix @ axis_transform
|
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: move to another file
|
# TODO: move to another file
|
||||||
class PSK_FH_import(FileHandler):
|
class PSK_FH_import(FileHandler):
|
||||||
bl_idname = 'PSK_FH_import'
|
bl_idname = 'PSK_FH_import'
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class PskImportResult:
|
|||||||
self.mesh_object: Optional[Object] = None
|
self.mesh_object: Optional[Object] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def root_object(self) -> Optional[Object]:
|
def root_object(self) -> 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
|
||||||
|
|
||||||
|
|
||||||
@@ -210,8 +210,9 @@ def import_psk(psk: Psk, context: Context, name: str, options: PskImportOptions)
|
|||||||
for face_index, face in enumerate(psk.faces):
|
for face_index, face in enumerate(psk.faces):
|
||||||
if face_index in invalid_face_indices:
|
if face_index in invalid_face_indices:
|
||||||
continue
|
continue
|
||||||
for wedge in map(lambda i: psk.wedges[i], reversed(face.wedge_indices)):
|
for wedge_index in reversed(face.wedge_indices):
|
||||||
uv_layer_data[uv_layer_data_index] = wedge.u, 1.0 - wedge.v
|
u, v = psk.extra_uvs[wedge_index_offset + wedge_index]
|
||||||
|
uv_layer_data[uv_layer_data_index] = u, 1.0 - v
|
||||||
uv_layer_data_index += 1
|
uv_layer_data_index += 1
|
||||||
wedge_index_offset += len(psk.wedges)
|
wedge_index_offset += len(psk.wedges)
|
||||||
uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}')
|
uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}')
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ def read_psk(path: str) -> Psk:
|
|||||||
case b'MRPHDATA':
|
case b'MRPHDATA':
|
||||||
_read_types(fp, Psk.MorphData, section, psk.morph_data)
|
_read_types(fp, Psk.MorphData, section, psk.morph_data)
|
||||||
case _:
|
case _:
|
||||||
if section.name.startswith(b'EXTRAUVS'):
|
if section.name.startswith(b'EXTRAUV'):
|
||||||
_read_types(fp, Vector2, section, psk.extra_uvs)
|
_read_types(fp, Vector2, section, psk.extra_uvs)
|
||||||
else:
|
else:
|
||||||
# Section is not handled, skip it.
|
# Section is not handled, skip it.
|
||||||
|
|||||||
@@ -220,6 +220,12 @@ def test_psk_import_extra_uvs():
|
|||||||
assert mesh_data.uv_layers[0].name == 'UVMap', "First UV layer should be named 'UVMap'"
|
assert mesh_data.uv_layers[0].name == 'UVMap', "First UV layer should be named 'UVMap'"
|
||||||
assert mesh_data.uv_layers[1].name == 'EXTRAUV0', "Second UV layer should be named 'EXTRAUV0'"
|
assert mesh_data.uv_layers[1].name == 'EXTRAUV0', "Second UV layer should be named 'EXTRAUV0'"
|
||||||
|
|
||||||
|
# Verify that the data is actually different
|
||||||
|
assert mesh_data.uv_layers[0].uv[0].vector.x == 0.92480468750
|
||||||
|
assert mesh_data.uv_layers[0].uv[0].vector.y == 0.90533447265625
|
||||||
|
assert mesh_data.uv_layers[1].uv[0].vector.x == 3.0517578125e-05
|
||||||
|
assert mesh_data.uv_layers[1].uv[0].vector.y == 0.999969482421875
|
||||||
|
|
||||||
|
|
||||||
def test_psk_import_materials():
|
def test_psk_import_materials():
|
||||||
assert bpy.ops.psk.import_file(
|
assert bpy.ops.psk.import_file(
|
||||||
|
|||||||
Reference in New Issue
Block a user