diff --git a/io_scene_psk_psa/__init__.py b/io_scene_psk_psa/__init__.py index 44b2ddd..5d8f735 100644 --- a/io_scene_psk_psa/__init__.py +++ b/io_scene_psk_psa/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name": "PSK/PSA Importer/Exporter", "author": "Colin Basnett", - "version": (2, 1, 0), + "version": (3, 0, 0), "blender": (2, 80, 0), # "location": "File > Export > PSK Export (.psk)", "description": "PSK/PSA Import/Export (.psk/.psa)", diff --git a/io_scene_psk_psa/helpers.py b/io_scene_psk_psa/helpers.py index f5864b2..8c5c69c 100644 --- a/io_scene_psk_psa/helpers.py +++ b/io_scene_psk_psa/helpers.py @@ -1,7 +1,8 @@ -from bpy.types import NlaStrip -from typing import List, Tuple, Optional -from collections import Counter import datetime +from collections import Counter +from typing import List + +from bpy.types import NlaStrip class Timer: diff --git a/io_scene_psk_psa/psa/builder.py b/io_scene_psk_psa/psa/builder.py index 0a24a34..ae953c1 100644 --- a/io_scene_psk_psa/psa/builder.py +++ b/io_scene_psk_psa/psa/builder.py @@ -1,7 +1,9 @@ +from typing import Dict, Iterable + +from bpy.types import Action + from .data import * from ..helpers import * -from typing import Dict, Iterable -from bpy.types import Action class PsaBuilderOptions(object): @@ -37,13 +39,13 @@ class PsaBuilder(object): return options.fps_custom elif options.fps_source == 'ACTION_METADATA': # Get the minimum value of action metadata FPS values. - psa_fps_list = [] - for action in filter(lambda x: 'psa_fps' in x, actions): - psa_fps = action['psa_fps'] - if type(psa_fps) == int or type(psa_fps) == float: - psa_fps_list.append(psa_fps) - if len(psa_fps_list) > 0: - return min(psa_fps_list) + fps_list = [] + for action in filter(lambda x: 'psa_sequence_fps' in x, actions): + fps = action['psa_sequence_fps'] + if type(fps) == int or type(fps) == float: + fps_list.append(fps) + if len(fps_list) > 0: + return min(fps_list) else: # No valid action metadata to use, fallback to scene FPS return context.scene.render.fps @@ -166,7 +168,8 @@ class PsaBuilder(object): export_sequence.nla_state.action = None export_sequence.nla_state.frame_min = frame_min export_sequence.nla_state.frame_max = frame_max - nla_strips_actions = set(map(lambda x: x.action, get_nla_strips_in_timeframe(active_object, frame_min, frame_max))) + nla_strips_actions = set( + map(lambda x: x.action, get_nla_strips_in_timeframe(active_object, frame_min, frame_max))) export_sequence.fps = self.get_sequence_fps(context, options, nla_strips_actions) export_sequences.append(export_sequence) else: diff --git a/io_scene_psk_psa/psa/data.py b/io_scene_psk_psa/psa/data.py index 6f4e484..76395d1 100644 --- a/io_scene_psk_psa/psa/data.py +++ b/io_scene_psk_psa/psa/data.py @@ -1,6 +1,7 @@ import typing -from typing import List from collections import OrderedDict +from typing import List + from ..data import * """ diff --git a/io_scene_psk_psa/psa/exporter.py b/io_scene_psk_psa/psa/exporter.py index fd5d15e..3f76655 100644 --- a/io_scene_psk_psa/psa/exporter.py +++ b/io_scene_psk_psa/psa/exporter.py @@ -1,16 +1,19 @@ -import bpy -from bpy.types import Operator, PropertyGroup, Action, UIList, BoneGroup, Panel, TimelineMarker -from bpy.props import CollectionProperty, IntProperty, FloatProperty, PointerProperty, StringProperty, BoolProperty, EnumProperty -from bpy_extras.io_utils import ExportHelper -from typing import Type -from .builder import PsaBuilder, PsaBuilderOptions -from .data import * -from ..types import BoneGroupListItem -from ..helpers import * -from collections import Counter +import fnmatch import re import sys -import fnmatch +from collections import Counter +from typing import Type + +import bpy +from bpy.props import BoolProperty, CollectionProperty, EnumProperty, FloatProperty, IntProperty, PointerProperty, \ + StringProperty +from bpy.types import Action, Operator, PropertyGroup, UIList +from bpy_extras.io_utils import ExportHelper + +from .builder import PsaBuilder, PsaBuilderOptions +from .data import * +from ..helpers import * +from ..types import BoneGroupListItem class PsaExporter(object): @@ -57,7 +60,7 @@ def update_action_names(context): item.action_name = get_psa_sequence_name(action, pg.should_use_original_sequence_names) -def should_use_original_sequence_names_updated(property, context): +def should_use_original_sequence_names_updated(_, context): update_action_names(context) @@ -68,7 +71,8 @@ class PsaExportPropertyGroup(PropertyGroup): description='', items=( ('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0), - ('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences will be exported using timeline markers', 'MARKER_HLT', 1), + ('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences will be exported using timeline markers', 'MARKER_HLT', + 1), ) ) fps_source: EnumProperty( @@ -77,11 +81,14 @@ class PsaExportPropertyGroup(PropertyGroup): description='', items=( ('SCENE', 'Scene', '', 'SCENE_DATA', 0), - ('ACTION_METADATA', 'Action Metadata', 'The frame rate will be determined by action\'s "psa_fps" custom property, if it exists. If the Sequence Source is Timeline Markers, the lowest value of all contributing actions will be used. If no metadata is available, the scene\'s frame rate will be used.', 'PROPERTIES', 1), + ('ACTION_METADATA', 'Action Metadata', + 'The frame rate will be determined by action\'s "psa_sequence_fps" custom property, if it exists. If the Sequence Source is Timeline Markers, the lowest value of all contributing actions will be used. If no metadata is available, the scene\'s frame rate will be used.', + 'PROPERTIES', 1), ('CUSTOM', 'Custom', '', 2) ) ) - fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=set(), step=100, soft_max=60.0) + fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=set(), step=100, + soft_max=60.0) action_list: CollectionProperty(type=PsaExportActionListItem) action_list_index: IntProperty(default=0) marker_list: CollectionProperty(type=PsaExportTimelineMarkerListItem) @@ -117,7 +124,8 @@ class PsaExportPropertyGroup(PropertyGroup): sequence_name_suffix: StringProperty(name='Suffix', options=set()) sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'}) sequence_use_filter_invert: BoolProperty(default=False, options=set()) - sequence_filter_asset: BoolProperty(default=False, name='Show assets', description='Show actions that belong to an asset library', options=set()) + sequence_filter_asset: BoolProperty(default=False, name='Show assets', + description='Show actions that belong to an asset library', options=set()) sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set()) @@ -178,7 +186,8 @@ class PsaExportOperator(Operator, ExportHelper): elif pg.sequence_source == 'TIMELINE_MARKERS': rows = max(3, min(len(pg.marker_list), 10)) - layout.template_list('PSA_UL_ExportTimelineMarkerList', '', pg, 'marker_list', pg, 'marker_list_index', rows=rows) + layout.template_list('PSA_UL_ExportTimelineMarkerList', '', pg, 'marker_list', pg, 'marker_list_index', + rows=rows) col = layout.column() col.use_property_split = True @@ -208,7 +217,8 @@ class PsaExportOperator(Operator, ExportHelper): row.operator(PsaExportBoneGroupsSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT') row.operator(PsaExportBoneGroupsDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT') rows = max(3, min(len(pg.bone_group_list), 10)) - layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows) + layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', + rows=rows) def should_action_be_selected_by_default(self, action): return action is not None and action.asset_data is None @@ -336,7 +346,8 @@ def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_c return flt_flags -def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaExportActionListItem]: +def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[ + PsaExportActionListItem]: visible_sequences = [] for i, flag in enumerate(filter_sequences(pg, sequences)): if bool(flag & (1 << 30)): diff --git a/io_scene_psk_psa/psa/importer.py b/io_scene_psk_psa/psa/importer.py index bc7f2fe..6d3151f 100644 --- a/io_scene_psk_psa/psa/importer.py +++ b/io_scene_psk_psa/psa/importer.py @@ -1,14 +1,16 @@ -import bpy -import os -import numpy as np -import re import fnmatch -from mathutils import Vector, Quaternion, Matrix -from .data import Psa -from typing import List, AnyStr, Optional -from bpy.types import Operator, Action, UIList, PropertyGroup, Panel, Armature, FileSelectParams -from bpy_extras.io_utils import ImportHelper +import os +import re +from typing import List, Optional + +import bpy +import numpy as np from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty +from bpy.types import Operator, UIList, PropertyGroup, Panel +from bpy_extras.io_utils import ImportHelper +from mathutils import Vector, Quaternion + +from .data import Psa from .reader import PsaReader @@ -18,7 +20,6 @@ class PsaImportOptions(object): self.should_use_fake_user = False self.should_stash = False self.sequence_names = [] - self.should_use_action_name_prefix = False self.should_overwrite = False self.should_write_keyframes = True self.should_write_metadata = True @@ -76,7 +77,8 @@ class PsaImporter(object): # Report if there are missing bones in the target armature. missing_bone_names = set(psa_bone_names).difference(set(armature_bone_names)) if len(missing_bone_names) > 0: - print(f'The armature object \'{armature_object.name}\' is missing the following bones that exist in the PSA:') + print( + f'The armature object \'{armature_object.name}\' is missing the following bones that exist in the PSA:') print(list(sorted(missing_bone_names))) del armature_bone_names @@ -192,14 +194,16 @@ class PsaImporter(object): if bone_has_writeable_keyframes: # This bone has writeable keyframes for this frame. key_data = sequence_data_matrix[frame_index, bone_index] - for fcurve, should_write, datum in zip(import_bone.fcurves, keyframe_write_matrix[frame_index, bone_index], key_data): + for fcurve, should_write, datum in zip(import_bone.fcurves, + keyframe_write_matrix[frame_index, bone_index], + key_data): if should_write: fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'}) # Write if options.should_write_metadata: action['psa_sequence_name'] = sequence_name - action['psa_fps'] = sequence.fps + action['psa_sequence_fps'] = sequence.fps action.use_fake_user = options.should_use_fake_user @@ -259,18 +263,28 @@ class PsaImportPropertyGroup(PropertyGroup): psa: PointerProperty(type=PsaDataPropertyGroup) sequence_list: CollectionProperty(type=PsaImportActionListItem) sequence_list_index: IntProperty(name='', default=0) - should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', description='Exclude unnecessary keyframes from being written to the actions.', options=set()) - should_use_fake_user: BoolProperty(default=True, name='Fake User', description='Assign each imported action a fake user so that the data block is saved even it has no users.', options=set()) - should_stash: BoolProperty(default=False, name='Stash', description='Stash each imported action as a strip on a new non-contributing NLA track', options=set()) + should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', + description='Exclude unnecessary keyframes from being written to the actions', + options=set()) + should_use_fake_user: BoolProperty(default=True, name='Fake User', + description='Assign each imported action a fake user so that the data block is saved even it has no users', + options=set()) + should_stash: BoolProperty(default=False, name='Stash', + description='Stash each imported action as a strip on a new non-contributing NLA track', + options=set()) should_use_action_name_prefix: BoolProperty(default=False, name='Prefix Action Name', options=set()) - should_overwrite: BoolProperty(default=False, name='Reuse Existing Datablocks', options=set()) - should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=set()) - should_write_metadata: BoolProperty(default=True, name='Metadata', options=set(), description='Additional data will be written to the custom properties of the Action (e.g., frame rate)') action_name_prefix: StringProperty(default='', name='Prefix', options=set()) + should_overwrite: BoolProperty(default=False, name='Reuse Existing Actions', options=set(), + description='If an action with a matching name already exists, the existing action will have it\'s data overwritten instead of a new action being created') + should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=set()) + should_write_metadata: BoolProperty(default=True, name='Metadata', options=set(), + description='Additional data will be written to the custom properties of the Action (e.g., frame rate)') sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'}) - sequence_filter_is_selected: BoolProperty(default=False, options=set(), name='Only Show Selected', description='Only show selected sequences') + sequence_filter_is_selected: BoolProperty(default=False, options=set(), name='Only Show Selected', + description='Only show selected sequences') sequence_use_filter_invert: BoolProperty(default=False, options=set()) - sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression', description='Filter using regular expressions', options=set()) + sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression', + description='Filter using regular expressions', options=set()) select_text: PointerProperty(type=bpy.types.Text) @@ -290,7 +304,7 @@ def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_c except re.error: pass else: - # User regular matching + # User regular text matching. for i, sequence in enumerate(sequences): if not fnmatch.fnmatch(sequence.action_name, f'*{pg.sequence_filter_name}*'): flt_flags[i] &= ~bitflag_filter_item @@ -308,7 +322,8 @@ def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_c return flt_flags -def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaImportActionListItem]: +def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[ + PsaImportActionListItem]: bitflag_filter_item = 1 << 30 visible_sequences = [] for i, flag in enumerate(filter_sequences(pg, sequences)): @@ -451,21 +466,6 @@ class PSA_PT_ImportPanel_Advanced(Panel): col.prop(pg, 'action_name_prefix') -class PSA_PT_ImportPanel_PsaData(Panel): - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_label = 'PSA Info' - bl_options = {'DEFAULT_CLOSED'} - bl_parent_id = 'PSA_PT_ImportPanel' - - def draw(self, context): - layout = self.layout - pg = context.scene.psa_import.psa - - layout.label(text=f'{len(pg.bones)} Bones', icon='BONE_DATA') - layout.label(text=f'{pg.sequence_count} Sequences', icon='SEQUENCE') - - class PSA_PT_ImportPanel(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -584,7 +584,7 @@ class PsaImportOperator(Operator): options.should_clean_keys = pg.should_clean_keys options.should_use_fake_user = pg.should_use_fake_user options.should_stash = pg.should_stash - options.action_name_prefix = pg.action_name_prefix + options.action_name_prefix = pg.action_name_prefix if pg.should_use_action_name_prefix else '' options.should_overwrite = pg.should_overwrite options.should_write_metadata = pg.should_write_metadata options.should_write_keyframes = pg.should_write_keyframes @@ -632,7 +632,6 @@ classes = ( PsaImportFileReload, PSA_PT_ImportPanel, PSA_PT_ImportPanel_Advanced, - PSA_PT_ImportPanel_PsaData, PsaImportOperator, PsaImportFileSelectOperator, PsaImportSelectFile, diff --git a/io_scene_psk_psa/psa/reader.py b/io_scene_psk_psa/psa/reader.py index 5f4acd5..c8f41d0 100644 --- a/io_scene_psk_psa/psa/reader.py +++ b/io_scene_psk_psa/psa/reader.py @@ -1,14 +1,17 @@ -from .data import * import ctypes + import numpy as np +from .data import * + class PsaReader(object): """ - This class will read the sequences and bone information immediately upon instantiation and hold onto a file handle. + This class reads the sequences and bone information immediately upon instantiation and hold onto a file handle. The key data is not read into memory upon instantiation due to it's potentially very large size. To read the key data for a particular sequence, call `read_sequence_keys`. """ + def __init__(self, path): self.keys_data_offset: int = 0 self.fp = open(path, 'rb') @@ -22,15 +25,6 @@ class PsaReader(object): def sequences(self) -> OrderedDict[Psa.Sequence]: return self.psa.sequences - @staticmethod - def _read_types(fp, data_class: ctypes.Structure, section: Section, data): - buffer_length = section.data_size * section.data_count - buffer = fp.read(buffer_length) - offset = 0 - for _ in range(section.data_count): - data.append(data_class.from_buffer_copy(buffer, offset)) - offset += section.data_size - def read_sequence_data_matrix(self, sequence_name: str): sequence = self.psa.sequences[sequence_name] keys = self.read_sequence_keys(sequence_name) @@ -65,6 +59,15 @@ class PsaReader(object): offset += data_size return keys + @staticmethod + def _read_types(fp, data_class: ctypes.Structure, section: Section, data): + buffer_length = section.data_size * section.data_count + buffer = fp.read(buffer_length) + offset = 0 + for _ in range(section.data_count): + data.append(data_class.from_buffer_copy(buffer, offset)) + offset += section.data_size + def _read(self, fp) -> Psa: psa = Psa() while fp.read(1): @@ -88,4 +91,3 @@ class PsaReader(object): else: raise RuntimeError(f'Unrecognized section "{section.name}"') return psa -1 \ No newline at end of file diff --git a/io_scene_psk_psa/psk/builder.py b/io_scene_psk_psa/psk/builder.py index f48e139..4421600 100644 --- a/io_scene_psk_psa/psk/builder.py +++ b/io_scene_psk_psa/psk/builder.py @@ -1,6 +1,5 @@ -import bpy -import bmesh from collections import OrderedDict + from .data import * from ..helpers import * diff --git a/io_scene_psk_psa/psk/data.py b/io_scene_psk_psa/psk/data.py index e663a22..cb87058 100644 --- a/io_scene_psk_psa/psk/data.py +++ b/io_scene_psk_psa/psk/data.py @@ -1,9 +1,9 @@ from typing import List + from ..data import * class Psk(object): - class Wedge(object): def __init__(self): self.point_index: int = 0 diff --git a/io_scene_psk_psa/psk/exporter.py b/io_scene_psk_psa/psk/exporter.py index 9d047b7..3327940 100644 --- a/io_scene_psk_psa/psk/exporter.py +++ b/io_scene_psk_psa/psk/exporter.py @@ -1,11 +1,13 @@ -from .data import * -from ..types import BoneGroupListItem -from ..helpers import populate_bone_group_list -from .builder import PskBuilder, PskBuilderOptions from typing import Type + +from bpy.props import StringProperty, CollectionProperty, IntProperty, EnumProperty from bpy.types import Operator, PropertyGroup from bpy_extras.io_utils import ExportHelper -from bpy.props import StringProperty, CollectionProperty, IntProperty, BoolProperty, EnumProperty + +from .builder import PskBuilder, PskBuilderOptions +from .data import * +from ..helpers import populate_bone_group_list +from ..types import BoneGroupListItem MAX_WEDGE_COUNT = 65536 MAX_POINT_COUNT = 4294967296 @@ -144,7 +146,8 @@ class PskExportPropertyGroup(PropertyGroup): description='', items=( ('ALL', 'All', 'All bones will be exported.'), - ('BONE_GROUPS', 'Bone Groups', 'Only bones belonging to the selected bone groups and their ancestors will be exported.') + ('BONE_GROUPS', 'Bone Groups', + 'Only bones belonging to the selected bone groups and their ancestors will be exported.') ) ) bone_group_list: CollectionProperty(type=BoneGroupListItem) @@ -154,4 +157,4 @@ class PskExportPropertyGroup(PropertyGroup): classes = ( PskExportOperator, PskExportPropertyGroup -) \ No newline at end of file +) diff --git a/io_scene_psk_psa/psk/importer.py b/io_scene_psk_psa/psk/importer.py index 8de4e81..e2758df 100644 --- a/io_scene_psk_psa/psk/importer.py +++ b/io_scene_psk_psa/psk/importer.py @@ -1,16 +1,19 @@ import os -import bpy -import bmesh -import numpy as np +import sys from math import inf from typing import Optional -from .data import Psk -from ..helpers import rgb_to_srgb -from mathutils import Quaternion, Vector, Matrix -from .reader import PskReader -from bpy.props import StringProperty, EnumProperty, BoolProperty + +import bmesh +import bpy +import numpy as np +from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty from bpy.types import Operator, PropertyGroup from bpy_extras.io_utils import ImportHelper +from mathutils import Quaternion, Vector, Matrix + +from .data import Psk +from .reader import PskReader +from ..helpers import rgb_to_srgb class PskImportOptions(object): @@ -20,6 +23,7 @@ class PskImportOptions(object): self.vertex_color_space = 'sRGB' self.should_import_vertex_normals = True self.should_import_extra_uvs = True + self.bone_length = 1.0 class PskImporter(object): @@ -60,7 +64,6 @@ class PskImporter(object): self.post_quat: Quaternion = Quaternion() import_bones = [] - new_bone_size = 8.0 for bone_index, psk_bone in enumerate(psk.bones): import_bone = ImportBone(bone_index, psk_bone) @@ -93,7 +96,7 @@ class PskImporter(object): else: import_bone.local_rotation.conjugate() - edit_bone.tail = Vector((0.0, new_bone_size, 0.0)) + edit_bone.tail = Vector((0.0, options.bone_length, 0.0)) edit_bone_matrix = import_bone.local_rotation.conjugated() edit_bone_matrix.rotate(import_bone.world_matrix) edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4() @@ -209,7 +212,8 @@ class PskImporter(object): # Get a list of all bones that have weights associated with them. vertex_group_bone_indices = set(map(lambda weight: weight.bone_index, psk.weights)) for import_bone in map(lambda x: import_bones[x], sorted(list(vertex_group_bone_indices))): - import_bone.vertex_group = mesh_object.vertex_groups.new(name=import_bone.psk_bone.name.decode('windows-1252')) + import_bone.vertex_group = mesh_object.vertex_groups.new( + name=import_bone.psk_bone.name.decode('windows-1252')) for weight in psk.weights: import_bones[weight.bone_index].vertex_group.add((weight.point_index,), weight.weight, 'ADD') @@ -256,6 +260,15 @@ class PskImportPropertyGroup(PropertyGroup): options=set(), description='Import extra UV maps from PSKX files, if available' ) + bone_length: FloatProperty( + default=1.0, + min=sys.float_info.epsilon, + step=100, + soft_min=1.0, + name='Bone Length', + options=set(), + description='Length of the bones' + ) class PskImportOperator(Operator, ImportHelper): @@ -277,7 +290,11 @@ class PskImportOperator(Operator, ImportHelper): psk = reader.read(self.filepath) options = PskImportOptions() options.name = os.path.splitext(os.path.basename(self.filepath))[0] + options.should_import_extra_uvs = pg.should_import_extra_uvs + options.should_import_vertex_colors = pg.should_import_vertex_colors + options.should_import_vertex_normals = pg.should_import_vertex_normals options.vertex_color_space = pg.vertex_color_space + options.bone_length = pg.bone_length PskImporter().import_psk(psk, context, options) return {'FINISHED'} @@ -291,6 +308,7 @@ class PskImportOperator(Operator, ImportHelper): layout.prop(pg, 'should_import_vertex_colors') if pg.should_import_vertex_colors: layout.prop(pg, 'vertex_color_space') + layout.prop(pg, 'bone_length') classes = ( diff --git a/io_scene_psk_psa/psk/reader.py b/io_scene_psk_psa/psk/reader.py index 29eb4ba..7fde8b6 100644 --- a/io_scene_psk_psa/psk/reader.py +++ b/io_scene_psk_psa/psk/reader.py @@ -1,6 +1,7 @@ -from .data import * import ctypes +from .data import * + class PskReader(object): diff --git a/io_scene_psk_psa/types.py b/io_scene_psk_psa/types.py index 142f4fb..aa38595 100644 --- a/io_scene_psk_psa/types.py +++ b/io_scene_psk_psa/types.py @@ -1,5 +1,5 @@ -from bpy.types import PropertyGroup, UIList from bpy.props import StringProperty, IntProperty, BoolProperty +from bpy.types import PropertyGroup, UIList class PSX_UL_BoneGroupList(UIList):