1
0
mirror of https://github.com/DarklightGames/io_scene_psk_psa.git synced 2024-12-18 01:05:57 +01:00

Merge branch 'master' into blender-4.1

This commit is contained in:
Colin Basnett 2024-03-11 18:46:24 -07:00
commit b67c734687
10 changed files with 115 additions and 24 deletions

View File

@ -3,7 +3,7 @@ from bpy.app.handlers import persistent
bl_info = { bl_info = {
'name': 'PSK/PSA Importer/Exporter', 'name': 'PSK/PSA Importer/Exporter',
'author': 'Colin Basnett, Yurii Ti', 'author': 'Colin Basnett, Yurii Ti',
'version': (6, 1, 2), 'version': (6, 2, 0),
'blender': (4, 0, 0), 'blender': (4, 0, 0),
'description': 'PSK/PSA Import/Export (.psk/.psa)', 'description': 'PSK/PSA Import/Export (.psk/.psa)',
'warning': '', 'warning': '',
@ -24,6 +24,8 @@ if 'bpy' in locals():
importlib.reload(psk_writer) importlib.reload(psk_writer)
importlib.reload(psk_builder) importlib.reload(psk_builder)
importlib.reload(psk_importer) importlib.reload(psk_importer)
importlib.reload(psk_properties)
importlib.reload(psk_ui)
importlib.reload(psk_export_properties) importlib.reload(psk_export_properties)
importlib.reload(psk_export_operators) importlib.reload(psk_export_operators)
importlib.reload(psk_export_ui) importlib.reload(psk_export_ui)
@ -51,6 +53,8 @@ else:
from .psk import writer as psk_writer from .psk import writer as psk_writer
from .psk import builder as psk_builder from .psk import builder as psk_builder
from .psk import importer as psk_importer from .psk import importer as psk_importer
from .psk import properties as psk_properties
from .psk import ui as psk_ui
from .psk.export import properties as psk_export_properties from .psk.export import properties as psk_export_properties
from .psk.export import operators as psk_export_operators from .psk.export import operators as psk_export_operators
from .psk.export import ui as psk_export_ui from .psk.export import ui as psk_export_ui
@ -73,6 +77,8 @@ import bpy
from bpy.props import PointerProperty from bpy.props import PointerProperty
classes = psx_types.classes +\ classes = psx_types.classes +\
psk_properties.classes +\
psk_ui.classes +\
psk_import_operators.classes +\ psk_import_operators.classes +\
psk_export_properties.classes +\ psk_export_properties.classes +\
psk_export_operators.classes +\ psk_export_operators.classes +\
@ -108,6 +114,7 @@ def register():
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)
bpy.types.TOPBAR_MT_file_import.append(psa_import_menu_func) bpy.types.TOPBAR_MT_file_import.append(psa_import_menu_func)
bpy.types.Material.psk = PointerProperty(type=psk_properties.PSX_PG_material)
bpy.types.Scene.psa_import = PointerProperty(type=psa_import_properties.PSA_PG_import) bpy.types.Scene.psa_import = PointerProperty(type=psa_import_properties.PSA_PG_import)
bpy.types.Scene.psa_export = PointerProperty(type=psa_export_properties.PSA_PG_export) bpy.types.Scene.psa_export = PointerProperty(type=psa_export_properties.PSA_PG_export)
bpy.types.Scene.psk_export = PointerProperty(type=psk_export_properties.PSK_PG_export) bpy.types.Scene.psk_export = PointerProperty(type=psk_export_properties.PSK_PG_export)
@ -115,6 +122,7 @@ def register():
def unregister(): def unregister():
del bpy.types.Material.psk
del bpy.types.Scene.psa_import del bpy.types.Scene.psa_import
del bpy.types.Scene.psa_export del bpy.types.Scene.psa_export
del bpy.types.Scene.psk_export del bpy.types.Scene.psk_export

View File

@ -111,7 +111,7 @@ def get_animation_data_object(context: Context) -> Object:
if active_object.type != 'ARMATURE': if active_object.type != 'ARMATURE':
raise RuntimeError('Selected object must be an Armature') raise RuntimeError('Selected object must be an Armature')
if pg.should_override_animation_data: if pg.sequence_source != 'ACTIONS' and pg.should_override_animation_data:
animation_data_object = pg.animation_data_override animation_data_object = pg.animation_data_override
else: else:
animation_data_object = active_object animation_data_object = active_object

View File

@ -3,9 +3,10 @@ from typing import Optional
import bmesh import bmesh
import bpy import bpy
import numpy as np import numpy as np
from bpy.types import Armature from bpy.types import Armature, Material
from .data import * from .data import *
from .properties import triangle_type_and_bit_flags_to_poly_flags
from ..helpers import * from ..helpers import *
@ -20,7 +21,7 @@ class PskBuildOptions(object):
self.bone_filter_mode = 'ALL' self.bone_filter_mode = 'ALL'
self.bone_collection_indices: List[int] = [] self.bone_collection_indices: List[int] = []
self.use_raw_mesh_data = True self.use_raw_mesh_data = True
self.material_names: List[str] = [] self.materials: List[Material] = []
self.should_enforce_bone_name_restrictions = False self.should_enforce_bone_name_restrictions = False
@ -75,9 +76,9 @@ def build_psk(context, options: PskBuildOptions) -> PskBuildResult:
psk = Psk() psk = Psk()
bones = [] bones = []
if armature_object is None: if armature_object is None or len(armature_object.data.bones) == 0:
# If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement # If the mesh has no armature object or no bones, simply assign it a dummy bone at the root to satisfy the
# that a PSK file must have at least one bone. # requirement that a PSK file must have at least one bone.
psk_bone = Psk.Bone() psk_bone = Psk.Bone()
psk_bone.name = bytes('root', encoding='windows-1252') psk_bone.name = bytes('root', encoding='windows-1252')
psk_bone.flags = 0 psk_bone.flags = 0
@ -138,19 +139,21 @@ def build_psk(context, options: PskBuildOptions) -> PskBuildResult:
psk.bones.append(psk_bone) psk.bones.append(psk_bone)
# MATERIALS # MATERIALS
material_names = options.material_names for material in options.materials:
for material_name in material_names:
psk_material = Psk.Material() psk_material = Psk.Material()
try: try:
psk_material.name = bytes(material_name, encoding='windows-1252') psk_material.name = bytes(material.name, encoding='windows-1252')
except UnicodeEncodeError: except UnicodeEncodeError:
raise RuntimeError(f'Material name "{material_name}" contains characters that cannot be encoded in the Windows-1252 codepage') raise RuntimeError(f'Material name "{material.name}" contains characters that cannot be encoded in the Windows-1252 codepage')
psk_material.texture_index = len(psk.materials) psk_material.texture_index = len(psk.materials)
psk_material.poly_flags = triangle_type_and_bit_flags_to_poly_flags(material.psk.mesh_triangle_type,
material.psk.mesh_triangle_bit_flags)
psk.materials.append(psk_material) psk.materials.append(psk_material)
context.window_manager.progress_begin(0, len(input_objects.mesh_objects)) context.window_manager.progress_begin(0, len(input_objects.mesh_objects))
material_names = [m.name for m in options.materials]
for object_index, input_mesh_object in enumerate(input_objects.mesh_objects): for object_index, input_mesh_object in enumerate(input_objects.mesh_objects):
should_flip_normals = False should_flip_normals = False

View File

@ -20,19 +20,19 @@ def is_bone_filter_mode_item_available(context, identifier):
def populate_material_list(mesh_objects, material_list): def populate_material_list(mesh_objects, material_list):
material_list.clear() material_list.clear()
material_names = [] materials = []
for mesh_object in mesh_objects: for mesh_object in mesh_objects:
for i, material_slot in enumerate(mesh_object.material_slots): for i, material_slot in enumerate(mesh_object.material_slots):
material = material_slot.material material = material_slot.material
# TODO: put this in the poll arg? # TODO: put this in the poll arg?
if material is None: if material is None:
raise RuntimeError('Material slot cannot be empty (index ' + str(i) + ')') raise RuntimeError('Material slot cannot be empty (index ' + str(i) + ')')
if material.name not in material_names: if material not in materials:
material_names.append(material.name) materials.append(material)
for index, material_name in enumerate(material_names): for index, material in enumerate(materials):
m = material_list.add() m = material_list.add()
m.material_name = material_name m.material = material
m.index = index m.index = index
@ -159,9 +159,9 @@ class PSK_OT_export(Operator, ExportHelper):
options.bone_filter_mode = pg.bone_filter_mode options.bone_filter_mode = pg.bone_filter_mode
options.bone_collection_indices = [x.index for x in pg.bone_collection_list if x.is_selected] options.bone_collection_indices = [x.index for x in pg.bone_collection_list if x.is_selected]
options.use_raw_mesh_data = pg.use_raw_mesh_data options.use_raw_mesh_data = pg.use_raw_mesh_data
options.material_names = [m.material_name for m in pg.material_list] options.materials = [m.material for m in pg.material_list]
options.should_enforce_bone_name_restrictions = pg.should_enforce_bone_name_restrictions options.should_enforce_bone_name_restrictions = pg.should_enforce_bone_name_restrictions
try: try:
result = build_psk(context, options) result = build_psk(context, options)
for warning in result.warnings: for warning in result.warnings:

View File

@ -1,11 +1,11 @@
from bpy.props import EnumProperty, CollectionProperty, IntProperty, BoolProperty, StringProperty from bpy.props import EnumProperty, CollectionProperty, IntProperty, BoolProperty, PointerProperty
from bpy.types import PropertyGroup from bpy.types import PropertyGroup, Material
from ...types import PSX_PG_bone_collection_list_item from ...types import PSX_PG_bone_collection_list_item
class PSK_PG_material_list_item(PropertyGroup): class PSK_PG_material_list_item(PropertyGroup):
material_name: StringProperty() material: PointerProperty(type=Material)
index: IntProperty() index: IntProperty()

View File

@ -4,7 +4,7 @@ from bpy.types import UIList
class PSK_UL_materials(UIList): class PSK_UL_materials(UIList):
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_propname, index):
row = layout.row() row = layout.row()
row.label(text=str(getattr(item, 'material_name')), icon='MATERIAL') row.prop(item.material, 'name', text='', emboss=False, icon_value=layout.icon(item.material))
classes = ( classes = (

View File

@ -7,6 +7,7 @@ from bpy.types import VertexGroup
from mathutils import Quaternion, Vector, Matrix from mathutils import Quaternion, Vector, Matrix
from .data import Psk from .data import Psk
from .properties import poly_flags_to_triangle_type_and_bit_flags
from ..helpers import rgb_to_srgb, is_bdk_addon_loaded from ..helpers import rgb_to_srgb, is_bdk_addon_loaded
@ -134,6 +135,9 @@ def import_psk(psk: Psk, context, options: PskImportOptions) -> PskImportResult:
else: else:
# Just create a blank material. # Just create a blank material.
material = bpy.data.materials.new(material_name) material = bpy.data.materials.new(material_name)
mesh_triangle_type, mesh_triangle_bit_flags = poly_flags_to_triangle_type_and_bit_flags(psk_material.poly_flags)
material.psk.mesh_triangle_type = mesh_triangle_type
material.psk.mesh_triangle_bit_flags = mesh_triangle_bit_flags
material.use_nodes = True material.use_nodes = True
mesh_data.materials.append(material) mesh_data.materials.append(material)

View File

@ -0,0 +1,48 @@
from bpy.props import EnumProperty
from bpy.types import PropertyGroup
mesh_triangle_types_items = (
('NORMAL', 'Normal', 'Normal one-sided', 0),
('NORMAL_TWO_SIDED', 'Normal Two-Sided', 'Normal but two-sided', 1),
('TRANSLUCENT', 'Translucent', 'Translucent two-sided', 2),
('MASKED', 'Masked', 'Masked two-sided', 3),
('MODULATE', 'Modulate', 'Modulation blended two-sided', 4),
('PLACEHOLDER', 'Placeholder', 'Placeholder triangle for positioning weapon. Invisible', 8),
)
mesh_triangle_bit_flags_items = (
('UNLIT', 'Unlit', 'Full brightness, no lighting', 16),
('FLAT', 'Flat', 'Flat surface, don\'t do bMeshCurvy thing', 32),
('ENVIRONMENT', 'Environment', 'Environment mapped', 64),
('NO_SMOOTH', 'No Smooth', 'No bilinear filtering on this poly\'s texture', 128),
)
class PSX_PG_material(PropertyGroup):
mesh_triangle_type: EnumProperty(items=mesh_triangle_types_items, name='Triangle Type')
mesh_triangle_bit_flags: EnumProperty(items=mesh_triangle_bit_flags_items, name='Triangle Bit Flags',
options={'ENUM_FLAG'})
mesh_triangle_types_items_dict = {item[0]: item[3] for item in mesh_triangle_types_items}
mesh_triangle_bit_flags_items_dict = {item[0]: item[3] for item in mesh_triangle_bit_flags_items}
def triangle_type_and_bit_flags_to_poly_flags(mesh_triangle_type: str, mesh_triangle_bit_flags: set[str]) -> int:
poly_flags = 0
poly_flags |= mesh_triangle_types_items_dict.get(mesh_triangle_type, 0)
for flag in mesh_triangle_bit_flags:
poly_flags |= mesh_triangle_bit_flags_items_dict.get(flag, 0)
return poly_flags
def poly_flags_to_triangle_type_and_bit_flags(poly_flags: int) -> (str, set[str]):
try:
triangle_type = next(item[0] for item in mesh_triangle_types_items if item[3] == (poly_flags & 15))
except StopIteration:
triangle_type = 'NORMAL'
triangle_bit_flags = {item[0] for item in mesh_triangle_bit_flags_items if item[3] & poly_flags}
return triangle_type, triangle_bit_flags
classes = (
PSX_PG_material,
)

View File

@ -0,0 +1,28 @@
from bpy.types import Panel
class PSK_PT_material(Panel):
bl_label = 'PSK Material'
bl_idname = 'PSK_PT_material'
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = 'material'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return context.material is not None
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
material = context.material
layout.prop(material.psk, 'mesh_triangle_type')
col = layout.column()
col.prop(material.psk, 'mesh_triangle_bit_flags', expand=True, text='Flags')
classes = (
PSK_PT_material,
)

View File

@ -51,5 +51,5 @@ classes = (
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,
) )