Refactoring to reduce pointless class encapsulation when just a function would do.
This commit is contained in:
@@ -12,265 +12,261 @@ class PskInputObjects(object):
|
||||
self.armature_object = None
|
||||
|
||||
|
||||
class PskBuilderOptions(object):
|
||||
class PskBuildOptions(object):
|
||||
def __init__(self):
|
||||
self.bone_filter_mode = 'ALL'
|
||||
self.bone_group_indices = []
|
||||
self.use_raw_mesh_data = True
|
||||
|
||||
|
||||
class PskBuilder(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
def get_psk_input_objects(context) -> PskInputObjects:
|
||||
input_objects = PskInputObjects()
|
||||
for selected_object in context.view_layer.objects.selected:
|
||||
if selected_object.type != 'MESH':
|
||||
raise RuntimeError(f'Selected object "{selected_object.name}" is not a mesh')
|
||||
|
||||
@staticmethod
|
||||
def get_input_objects(context) -> PskInputObjects:
|
||||
input_objects = PskInputObjects()
|
||||
for selected_object in context.view_layer.objects.selected:
|
||||
if selected_object.type != 'MESH':
|
||||
raise RuntimeError(f'Selected object "{selected_object.name}" is not a mesh')
|
||||
input_objects.mesh_objects = context.view_layer.objects.selected
|
||||
|
||||
input_objects.mesh_objects = context.view_layer.objects.selected
|
||||
if len(input_objects.mesh_objects) == 0:
|
||||
raise RuntimeError('At least one mesh must be selected')
|
||||
|
||||
if len(input_objects.mesh_objects) == 0:
|
||||
raise RuntimeError('At least one mesh must be selected')
|
||||
for mesh_object in input_objects.mesh_objects:
|
||||
if len(mesh_object.data.materials) == 0:
|
||||
raise RuntimeError(f'Mesh "{mesh_object.name}" must have at least one material')
|
||||
|
||||
for mesh_object in input_objects.mesh_objects:
|
||||
if len(mesh_object.data.materials) == 0:
|
||||
raise RuntimeError(f'Mesh "{mesh_object.name}" must have at least one material')
|
||||
# Ensure that there are either no armature modifiers (static mesh)
|
||||
# or that there is exactly one armature modifier object shared between
|
||||
# all selected meshes
|
||||
armature_modifier_objects = set()
|
||||
|
||||
# Ensure that there are either no armature modifiers (static mesh)
|
||||
# or that there is exactly one armature modifier object shared between
|
||||
# all selected meshes
|
||||
armature_modifier_objects = set()
|
||||
for mesh_object in input_objects.mesh_objects:
|
||||
modifiers = [x for x in mesh_object.modifiers if x.type == 'ARMATURE']
|
||||
if len(modifiers) == 0:
|
||||
continue
|
||||
elif len(modifiers) > 1:
|
||||
raise RuntimeError(f'Mesh "{mesh_object.name}" must have only one armature modifier')
|
||||
armature_modifier_objects.add(modifiers[0].object)
|
||||
|
||||
for mesh_object in input_objects.mesh_objects:
|
||||
modifiers = [x for x in mesh_object.modifiers if x.type == 'ARMATURE']
|
||||
if len(modifiers) == 0:
|
||||
continue
|
||||
elif len(modifiers) > 1:
|
||||
raise RuntimeError(f'Mesh "{mesh_object.name}" must have only one armature modifier')
|
||||
armature_modifier_objects.add(modifiers[0].object)
|
||||
if len(armature_modifier_objects) > 1:
|
||||
raise RuntimeError('All selected meshes must have the same armature modifier')
|
||||
elif len(armature_modifier_objects) == 1:
|
||||
input_objects.armature_object = list(armature_modifier_objects)[0]
|
||||
|
||||
if len(armature_modifier_objects) > 1:
|
||||
raise RuntimeError('All selected meshes must have the same armature modifier')
|
||||
elif len(armature_modifier_objects) == 1:
|
||||
input_objects.armature_object = list(armature_modifier_objects)[0]
|
||||
return input_objects
|
||||
|
||||
return input_objects
|
||||
|
||||
def build(self, context, options: PskBuilderOptions) -> Psk:
|
||||
input_objects = PskBuilder.get_input_objects(context)
|
||||
def build_psk(context, options: PskBuildOptions) -> Psk:
|
||||
input_objects = get_psk_input_objects(context)
|
||||
|
||||
armature_object = input_objects.armature_object
|
||||
armature_object = input_objects.armature_object
|
||||
|
||||
psk = Psk()
|
||||
bones = []
|
||||
materials = OrderedDict()
|
||||
psk = Psk()
|
||||
bones = []
|
||||
materials = OrderedDict()
|
||||
|
||||
if armature_object is None:
|
||||
# If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement
|
||||
# that a PSK file must have at least one bone.
|
||||
if armature_object is None:
|
||||
# If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement
|
||||
# that a PSK file must have at least one bone.
|
||||
psk_bone = Psk.Bone()
|
||||
psk_bone.name = bytes('root', encoding='windows-1252')
|
||||
psk_bone.flags = 0
|
||||
psk_bone.children_count = 0
|
||||
psk_bone.parent_index = 0
|
||||
psk_bone.location = Vector3.zero()
|
||||
psk_bone.rotation = Quaternion.identity()
|
||||
psk.bones.append(psk_bone)
|
||||
else:
|
||||
bone_names = get_export_bone_names(armature_object, options.bone_filter_mode, options.bone_group_indices)
|
||||
bones = [armature_object.data.bones[bone_name] for bone_name in bone_names]
|
||||
|
||||
# Check that all bone names are valid.
|
||||
check_bone_names(map(lambda x: x.name, bones))
|
||||
|
||||
for bone in bones:
|
||||
psk_bone = Psk.Bone()
|
||||
psk_bone.name = bytes('root', encoding='windows-1252')
|
||||
psk_bone.name = bytes(bone.name, encoding='windows-1252')
|
||||
psk_bone.flags = 0
|
||||
psk_bone.children_count = 0
|
||||
psk_bone.parent_index = 0
|
||||
psk_bone.location = Vector3.zero()
|
||||
psk_bone.rotation = Quaternion.identity()
|
||||
psk.bones.append(psk_bone)
|
||||
else:
|
||||
bone_names = get_export_bone_names(armature_object, options.bone_filter_mode, options.bone_group_indices)
|
||||
bones = [armature_object.data.bones[bone_name] for bone_name in bone_names]
|
||||
|
||||
# Check that all bone names are valid.
|
||||
check_bone_names(map(lambda x: x.name, bones))
|
||||
try:
|
||||
parent_index = bones.index(bone.parent)
|
||||
psk_bone.parent_index = parent_index
|
||||
psk.bones[parent_index].children_count += 1
|
||||
except ValueError:
|
||||
psk_bone.parent_index = 0
|
||||
|
||||
for bone in bones:
|
||||
psk_bone = Psk.Bone()
|
||||
psk_bone.name = bytes(bone.name, encoding='windows-1252')
|
||||
psk_bone.flags = 0
|
||||
psk_bone.children_count = 0
|
||||
|
||||
try:
|
||||
parent_index = bones.index(bone.parent)
|
||||
psk_bone.parent_index = parent_index
|
||||
psk.bones[parent_index].children_count += 1
|
||||
except ValueError:
|
||||
psk_bone.parent_index = 0
|
||||
|
||||
if bone.parent is not None:
|
||||
rotation = bone.matrix.to_quaternion()
|
||||
rotation.x = -rotation.x
|
||||
rotation.y = -rotation.y
|
||||
rotation.z = -rotation.z
|
||||
quat_parent = bone.parent.matrix.to_quaternion().inverted()
|
||||
parent_head = quat_parent @ bone.parent.head
|
||||
parent_tail = quat_parent @ bone.parent.tail
|
||||
location = (parent_tail - parent_head) + bone.head
|
||||
else:
|
||||
location = armature_object.matrix_local @ bone.head
|
||||
rot_matrix = bone.matrix @ armature_object.matrix_local.to_3x3()
|
||||
rotation = rot_matrix.to_quaternion()
|
||||
|
||||
psk_bone.location.x = location.x
|
||||
psk_bone.location.y = location.y
|
||||
psk_bone.location.z = location.z
|
||||
|
||||
psk_bone.rotation.x = rotation.x
|
||||
psk_bone.rotation.y = rotation.y
|
||||
psk_bone.rotation.z = rotation.z
|
||||
psk_bone.rotation.w = rotation.w
|
||||
|
||||
psk.bones.append(psk_bone)
|
||||
|
||||
for input_mesh_object in input_objects.mesh_objects:
|
||||
|
||||
# MATERIALS
|
||||
material_indices = []
|
||||
for i, material in enumerate(input_mesh_object.data.materials):
|
||||
if material is None:
|
||||
raise RuntimeError('Material cannot be empty (index ' + str(i) + ')')
|
||||
if material.name in materials:
|
||||
# Material already evaluated, just get its index.
|
||||
material_index = list(materials.keys()).index(material.name)
|
||||
else:
|
||||
# New material.
|
||||
psk_material = Psk.Material()
|
||||
psk_material.name = bytes(material.name, encoding='windows-1252')
|
||||
psk_material.texture_index = len(psk.materials)
|
||||
psk.materials.append(psk_material)
|
||||
materials[material.name] = material
|
||||
material_index = psk_material.texture_index
|
||||
material_indices.append(material_index)
|
||||
|
||||
if options.use_raw_mesh_data:
|
||||
mesh_object = input_mesh_object
|
||||
mesh_data = input_mesh_object.data
|
||||
if bone.parent is not None:
|
||||
rotation = bone.matrix.to_quaternion()
|
||||
rotation.x = -rotation.x
|
||||
rotation.y = -rotation.y
|
||||
rotation.z = -rotation.z
|
||||
quat_parent = bone.parent.matrix.to_quaternion().inverted()
|
||||
parent_head = quat_parent @ bone.parent.head
|
||||
parent_tail = quat_parent @ bone.parent.tail
|
||||
location = (parent_tail - parent_head) + bone.head
|
||||
else:
|
||||
# Create a copy of the mesh object after non-armature modifiers are applied.
|
||||
location = armature_object.matrix_local @ bone.head
|
||||
rot_matrix = bone.matrix @ armature_object.matrix_local.to_3x3()
|
||||
rotation = rot_matrix.to_quaternion()
|
||||
|
||||
# Temporarily deactivate any armature modifiers on the input mesh object.
|
||||
active_armature_modifiers = [x for x in filter(lambda x: x.type == 'ARMATURE' and x.is_active, input_mesh_object.modifiers)]
|
||||
for modifier in active_armature_modifiers:
|
||||
modifier.show_viewport = False
|
||||
psk_bone.location.x = location.x
|
||||
psk_bone.location.y = location.y
|
||||
psk_bone.location.z = location.z
|
||||
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
bm = bmesh.new()
|
||||
bm.from_object(input_mesh_object, depsgraph)
|
||||
mesh_data = bpy.data.meshes.new('')
|
||||
bm.to_mesh(mesh_data)
|
||||
del bm
|
||||
mesh_object = bpy.data.objects.new('', mesh_data)
|
||||
mesh_object.matrix_world = input_mesh_object.matrix_world
|
||||
psk_bone.rotation.x = rotation.x
|
||||
psk_bone.rotation.y = rotation.y
|
||||
psk_bone.rotation.z = rotation.z
|
||||
psk_bone.rotation.w = rotation.w
|
||||
|
||||
# Copy the vertex groups
|
||||
for vertex_group in input_mesh_object.vertex_groups:
|
||||
mesh_object.vertex_groups.new(name=vertex_group.name)
|
||||
psk.bones.append(psk_bone)
|
||||
|
||||
# Reactivate previously active armature modifiers
|
||||
for modifier in active_armature_modifiers:
|
||||
modifier.show_viewport = True
|
||||
for input_mesh_object in input_objects.mesh_objects:
|
||||
|
||||
vertex_offset = len(psk.points)
|
||||
# MATERIALS
|
||||
material_indices = []
|
||||
for i, material in enumerate(input_mesh_object.data.materials):
|
||||
if material is None:
|
||||
raise RuntimeError('Material cannot be empty (index ' + str(i) + ')')
|
||||
if material.name in materials:
|
||||
# Material already evaluated, just get its index.
|
||||
material_index = list(materials.keys()).index(material.name)
|
||||
else:
|
||||
# New material.
|
||||
psk_material = Psk.Material()
|
||||
psk_material.name = bytes(material.name, encoding='windows-1252')
|
||||
psk_material.texture_index = len(psk.materials)
|
||||
psk.materials.append(psk_material)
|
||||
materials[material.name] = material
|
||||
material_index = psk_material.texture_index
|
||||
material_indices.append(material_index)
|
||||
|
||||
# VERTICES
|
||||
for vertex in mesh_data.vertices:
|
||||
point = Vector3()
|
||||
v = mesh_object.matrix_world @ vertex.co
|
||||
point.x = v.x
|
||||
point.y = v.y
|
||||
point.z = v.z
|
||||
psk.points.append(point)
|
||||
if options.use_raw_mesh_data:
|
||||
mesh_object = input_mesh_object
|
||||
mesh_data = input_mesh_object.data
|
||||
else:
|
||||
# Create a copy of the mesh object after non-armature modifiers are applied.
|
||||
|
||||
uv_layer = mesh_data.uv_layers.active.data
|
||||
# Temporarily deactivate any armature modifiers on the input mesh object.
|
||||
active_armature_modifiers = [x for x in filter(lambda x: x.type == 'ARMATURE' and x.is_active, input_mesh_object.modifiers)]
|
||||
for modifier in active_armature_modifiers:
|
||||
modifier.show_viewport = False
|
||||
|
||||
# WEDGES
|
||||
mesh_data.calc_loop_triangles()
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
bm = bmesh.new()
|
||||
bm.from_object(input_mesh_object, depsgraph)
|
||||
mesh_data = bpy.data.meshes.new('')
|
||||
bm.to_mesh(mesh_data)
|
||||
del bm
|
||||
mesh_object = bpy.data.objects.new('', mesh_data)
|
||||
mesh_object.matrix_world = input_mesh_object.matrix_world
|
||||
|
||||
# Build a list of non-unique wedges.
|
||||
wedges = []
|
||||
for loop_index, loop in enumerate(mesh_data.loops):
|
||||
wedge = Psk.Wedge()
|
||||
wedge.point_index = loop.vertex_index + vertex_offset
|
||||
wedge.u, wedge.v = uv_layer[loop_index].uv
|
||||
wedge.v = 1.0 - wedge.v
|
||||
wedges.append(wedge)
|
||||
# Copy the vertex groups
|
||||
for vertex_group in input_mesh_object.vertex_groups:
|
||||
mesh_object.vertex_groups.new(name=vertex_group.name)
|
||||
|
||||
# Assign material indices to the wedges.
|
||||
for triangle in mesh_data.loop_triangles:
|
||||
for loop_index in triangle.loops:
|
||||
wedges[loop_index].material_index = material_indices[triangle.material_index]
|
||||
# Reactivate previously active armature modifiers
|
||||
for modifier in active_armature_modifiers:
|
||||
modifier.show_viewport = True
|
||||
|
||||
# Populate the list of wedges with unique wedges & build a look-up table of loop indices to wedge indices
|
||||
wedge_indices = {}
|
||||
loop_wedge_indices = [-1] * len(mesh_data.loops)
|
||||
for loop_index, wedge in enumerate(wedges):
|
||||
wedge_hash = hash(wedge)
|
||||
if wedge_hash in wedge_indices:
|
||||
loop_wedge_indices[loop_index] = wedge_indices[wedge_hash]
|
||||
else:
|
||||
wedge_index = len(psk.wedges)
|
||||
wedge_indices[wedge_hash] = wedge_index
|
||||
psk.wedges.append(wedge)
|
||||
loop_wedge_indices[loop_index] = wedge_index
|
||||
vertex_offset = len(psk.points)
|
||||
|
||||
# FACES
|
||||
poly_groups, groups = mesh_data.calc_smooth_groups(use_bitflags=True)
|
||||
for f in mesh_data.loop_triangles:
|
||||
face = Psk.Face()
|
||||
face.material_index = material_indices[f.material_index]
|
||||
face.wedge_indices[0] = loop_wedge_indices[f.loops[2]]
|
||||
face.wedge_indices[1] = loop_wedge_indices[f.loops[1]]
|
||||
face.wedge_indices[2] = loop_wedge_indices[f.loops[0]]
|
||||
face.smoothing_groups = poly_groups[f.polygon_index]
|
||||
psk.faces.append(face)
|
||||
# VERTICES
|
||||
for vertex in mesh_data.vertices:
|
||||
point = Vector3()
|
||||
v = mesh_object.matrix_world @ vertex.co
|
||||
point.x = v.x
|
||||
point.y = v.y
|
||||
point.z = v.z
|
||||
psk.points.append(point)
|
||||
|
||||
# WEIGHTS
|
||||
if armature_object is not None:
|
||||
# Because the vertex groups may contain entries for which there is no matching bone in the armature,
|
||||
# we must filter them out and not export any weights for these vertex groups.
|
||||
bone_names = [x.name for x in bones]
|
||||
vertex_group_names = [x.name for x in mesh_object.vertex_groups]
|
||||
vertex_group_bone_indices = dict()
|
||||
for vertex_group_index, vertex_group_name in enumerate(vertex_group_names):
|
||||
uv_layer = mesh_data.uv_layers.active.data
|
||||
|
||||
# WEDGES
|
||||
mesh_data.calc_loop_triangles()
|
||||
|
||||
# Build a list of non-unique wedges.
|
||||
wedges = []
|
||||
for loop_index, loop in enumerate(mesh_data.loops):
|
||||
wedge = Psk.Wedge()
|
||||
wedge.point_index = loop.vertex_index + vertex_offset
|
||||
wedge.u, wedge.v = uv_layer[loop_index].uv
|
||||
wedge.v = 1.0 - wedge.v
|
||||
wedges.append(wedge)
|
||||
|
||||
# Assign material indices to the wedges.
|
||||
for triangle in mesh_data.loop_triangles:
|
||||
for loop_index in triangle.loops:
|
||||
wedges[loop_index].material_index = material_indices[triangle.material_index]
|
||||
|
||||
# Populate the list of wedges with unique wedges & build a look-up table of loop indices to wedge indices
|
||||
wedge_indices = {}
|
||||
loop_wedge_indices = [-1] * len(mesh_data.loops)
|
||||
for loop_index, wedge in enumerate(wedges):
|
||||
wedge_hash = hash(wedge)
|
||||
if wedge_hash in wedge_indices:
|
||||
loop_wedge_indices[loop_index] = wedge_indices[wedge_hash]
|
||||
else:
|
||||
wedge_index = len(psk.wedges)
|
||||
wedge_indices[wedge_hash] = wedge_index
|
||||
psk.wedges.append(wedge)
|
||||
loop_wedge_indices[loop_index] = wedge_index
|
||||
|
||||
# FACES
|
||||
poly_groups, groups = mesh_data.calc_smooth_groups(use_bitflags=True)
|
||||
for f in mesh_data.loop_triangles:
|
||||
face = Psk.Face()
|
||||
face.material_index = material_indices[f.material_index]
|
||||
face.wedge_indices[0] = loop_wedge_indices[f.loops[2]]
|
||||
face.wedge_indices[1] = loop_wedge_indices[f.loops[1]]
|
||||
face.wedge_indices[2] = loop_wedge_indices[f.loops[0]]
|
||||
face.smoothing_groups = poly_groups[f.polygon_index]
|
||||
psk.faces.append(face)
|
||||
|
||||
# WEIGHTS
|
||||
if armature_object is not None:
|
||||
# Because the vertex groups may contain entries for which there is no matching bone in the armature,
|
||||
# we must filter them out and not export any weights for these vertex groups.
|
||||
bone_names = [x.name for x in bones]
|
||||
vertex_group_names = [x.name for x in mesh_object.vertex_groups]
|
||||
vertex_group_bone_indices = dict()
|
||||
for vertex_group_index, vertex_group_name in enumerate(vertex_group_names):
|
||||
try:
|
||||
vertex_group_bone_indices[vertex_group_index] = bone_names.index(vertex_group_name)
|
||||
except ValueError:
|
||||
# The vertex group does not have a matching bone in the list of bones to be exported.
|
||||
# Check to see if there is an associated bone for this vertex group that exists in the armature.
|
||||
# If there is, we can traverse the ancestors of that bone to find an alternate bone to use for
|
||||
# weighting the vertices belonging to this vertex group.
|
||||
if vertex_group_name in armature_object.data.bones:
|
||||
bone = armature_object.data.bones[vertex_group_name]
|
||||
while bone is not None:
|
||||
try:
|
||||
bone_index = bone_names.index(bone.name)
|
||||
vertex_group_bone_indices[vertex_group_index] = bone_index
|
||||
break
|
||||
except ValueError:
|
||||
bone = bone.parent
|
||||
for vertex_group_index, vertex_group in enumerate(mesh_object.vertex_groups):
|
||||
if vertex_group_index not in vertex_group_bone_indices:
|
||||
# Vertex group has no associated bone, skip it.
|
||||
continue
|
||||
bone_index = vertex_group_bone_indices[vertex_group_index]
|
||||
for vertex_index in range(len(mesh_data.vertices)):
|
||||
try:
|
||||
vertex_group_bone_indices[vertex_group_index] = bone_names.index(vertex_group_name)
|
||||
except ValueError:
|
||||
# The vertex group does not have a matching bone in the list of bones to be exported.
|
||||
# Check to see if there is an associated bone for this vertex group that exists in the armature.
|
||||
# If there is, we can traverse the ancestors of that bone to find an alternate bone to use for
|
||||
# weighting the vertices belonging to this vertex group.
|
||||
if vertex_group_name in armature_object.data.bones:
|
||||
bone = armature_object.data.bones[vertex_group_name]
|
||||
while bone is not None:
|
||||
try:
|
||||
bone_index = bone_names.index(bone.name)
|
||||
vertex_group_bone_indices[vertex_group_index] = bone_index
|
||||
break
|
||||
except ValueError:
|
||||
bone = bone.parent
|
||||
for vertex_group_index, vertex_group in enumerate(mesh_object.vertex_groups):
|
||||
if vertex_group_index not in vertex_group_bone_indices:
|
||||
# Vertex group has no associated bone, skip it.
|
||||
weight = vertex_group.weight(vertex_index)
|
||||
except RuntimeError:
|
||||
continue
|
||||
bone_index = vertex_group_bone_indices[vertex_group_index]
|
||||
for vertex_index in range(len(mesh_data.vertices)):
|
||||
try:
|
||||
weight = vertex_group.weight(vertex_index)
|
||||
except RuntimeError:
|
||||
continue
|
||||
if weight == 0.0:
|
||||
continue
|
||||
w = Psk.Weight()
|
||||
w.bone_index = bone_index
|
||||
w.point_index = vertex_offset + vertex_index
|
||||
w.weight = weight
|
||||
psk.weights.append(w)
|
||||
if weight == 0.0:
|
||||
continue
|
||||
w = Psk.Weight()
|
||||
w.bone_index = bone_index
|
||||
w.point_index = vertex_offset + vertex_index
|
||||
w.weight = weight
|
||||
psk.weights.append(w)
|
||||
|
||||
if not options.use_raw_mesh_data:
|
||||
bpy.data.objects.remove(mesh_object)
|
||||
bpy.data.meshes.remove(mesh_data)
|
||||
del mesh_data
|
||||
if not options.use_raw_mesh_data:
|
||||
bpy.data.objects.remove(mesh_object)
|
||||
bpy.data.meshes.remove(mesh_data)
|
||||
del mesh_data
|
||||
|
||||
return psk
|
||||
return psk
|
||||
|
||||
@@ -4,7 +4,7 @@ from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntPrope
|
||||
from bpy.types import Operator, PropertyGroup
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
|
||||
from .builder import PskBuilder, PskBuilderOptions
|
||||
from .builder import build_psk, PskBuildOptions, get_psk_input_objects
|
||||
from .data import *
|
||||
from ..helpers import populate_bone_group_list
|
||||
from ..types import BoneGroupListItem
|
||||
@@ -15,55 +15,50 @@ MAX_BONE_COUNT = 256
|
||||
MAX_MATERIAL_COUNT = 256
|
||||
|
||||
|
||||
class PskExporter(object):
|
||||
def _write_section(fp, name: bytes, data_type: Type[Structure] = None, data: list = None):
|
||||
section = Section()
|
||||
section.name = name
|
||||
if data_type is not None and data is not None:
|
||||
section.data_size = sizeof(data_type)
|
||||
section.data_count = len(data)
|
||||
fp.write(section)
|
||||
if data is not None:
|
||||
for datum in data:
|
||||
fp.write(datum)
|
||||
|
||||
def __init__(self, psk: Psk):
|
||||
self.psk: Psk = psk
|
||||
|
||||
@staticmethod
|
||||
def write_section(fp, name: bytes, data_type: Type[Structure] = None, data: list = None):
|
||||
section = Section()
|
||||
section.name = name
|
||||
if data_type is not None and data is not None:
|
||||
section.data_size = sizeof(data_type)
|
||||
section.data_count = len(data)
|
||||
fp.write(section)
|
||||
if data is not None:
|
||||
for datum in data:
|
||||
fp.write(datum)
|
||||
def export_psk(psk: Psk, path: str):
|
||||
if len(psk.wedges) > MAX_WEDGE_COUNT:
|
||||
raise RuntimeError(f'Number of wedges ({len(psk.wedges)}) exceeds limit of {MAX_WEDGE_COUNT}')
|
||||
if len(psk.bones) > MAX_BONE_COUNT:
|
||||
raise RuntimeError(f'Number of bones ({len(psk.bones)}) exceeds limit of {MAX_BONE_COUNT}')
|
||||
if len(psk.points) > MAX_POINT_COUNT:
|
||||
raise RuntimeError(f'Numbers of vertices ({len(psk.points)}) exceeds limit of {MAX_POINT_COUNT}')
|
||||
if len(psk.materials) > MAX_MATERIAL_COUNT:
|
||||
raise RuntimeError(f'Number of materials ({len(psk.materials)}) exceeds limit of {MAX_MATERIAL_COUNT}')
|
||||
|
||||
def export(self, path: str):
|
||||
if len(self.psk.wedges) > MAX_WEDGE_COUNT:
|
||||
raise RuntimeError(f'Number of wedges ({len(self.psk.wedges)}) exceeds limit of {MAX_WEDGE_COUNT}')
|
||||
if len(self.psk.bones) > MAX_BONE_COUNT:
|
||||
raise RuntimeError(f'Number of bones ({len(self.psk.bones)}) exceeds limit of {MAX_BONE_COUNT}')
|
||||
if len(self.psk.points) > MAX_POINT_COUNT:
|
||||
raise RuntimeError(f'Numbers of vertices ({len(self.psk.points)}) exceeds limit of {MAX_POINT_COUNT}')
|
||||
if len(self.psk.materials) > MAX_MATERIAL_COUNT:
|
||||
raise RuntimeError(f'Number of materials ({len(self.psk.materials)}) exceeds limit of {MAX_MATERIAL_COUNT}')
|
||||
with open(path, 'wb') as fp:
|
||||
_write_section(fp, b'ACTRHEAD')
|
||||
_write_section(fp, b'PNTS0000', Vector3, psk.points)
|
||||
|
||||
with open(path, 'wb') as fp:
|
||||
self.write_section(fp, b'ACTRHEAD')
|
||||
self.write_section(fp, b'PNTS0000', Vector3, self.psk.points)
|
||||
wedges = []
|
||||
for index, w in enumerate(psk.wedges):
|
||||
wedge = Psk.Wedge16()
|
||||
wedge.material_index = w.material_index
|
||||
wedge.u = w.u
|
||||
wedge.v = w.v
|
||||
wedge.point_index = w.point_index
|
||||
wedges.append(wedge)
|
||||
|
||||
wedges = []
|
||||
for index, w in enumerate(self.psk.wedges):
|
||||
wedge = Psk.Wedge16()
|
||||
wedge.material_index = w.material_index
|
||||
wedge.u = w.u
|
||||
wedge.v = w.v
|
||||
wedge.point_index = w.point_index
|
||||
wedges.append(wedge)
|
||||
|
||||
self.write_section(fp, b'VTXW0000', Psk.Wedge16, wedges)
|
||||
self.write_section(fp, b'FACE0000', Psk.Face, self.psk.faces)
|
||||
self.write_section(fp, b'MATT0000', Psk.Material, self.psk.materials)
|
||||
self.write_section(fp, b'REFSKELT', Psk.Bone, self.psk.bones)
|
||||
self.write_section(fp, b'RAWWEIGHTS', Psk.Weight, self.psk.weights)
|
||||
_write_section(fp, b'VTXW0000', Psk.Wedge16, wedges)
|
||||
_write_section(fp, b'FACE0000', Psk.Face, psk.faces)
|
||||
_write_section(fp, b'MATT0000', Psk.Material, psk.materials)
|
||||
_write_section(fp, b'REFSKELT', Psk.Bone, psk.bones)
|
||||
_write_section(fp, b'RAWWEIGHTS', Psk.Weight, psk.weights)
|
||||
|
||||
|
||||
def is_bone_filter_mode_item_available(context, identifier):
|
||||
input_objects = PskBuilder.get_input_objects(context)
|
||||
input_objects = get_psk_input_objects(context)
|
||||
armature_object = input_objects.armature_object
|
||||
if identifier == 'BONE_GROUPS':
|
||||
if not armature_object or not armature_object.pose or not armature_object.pose.bone_groups:
|
||||
@@ -88,7 +83,7 @@ class PskExportOperator(Operator, ExportHelper):
|
||||
|
||||
def invoke(self, context, event):
|
||||
try:
|
||||
input_objects = PskBuilder.get_input_objects(context)
|
||||
input_objects = get_psk_input_objects(context)
|
||||
except RuntimeError as e:
|
||||
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||
return {'CANCELLED'}
|
||||
@@ -105,7 +100,7 @@ class PskExportOperator(Operator, ExportHelper):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try:
|
||||
PskBuilder.get_input_objects(context)
|
||||
get_psk_input_objects(context)
|
||||
except RuntimeError as e:
|
||||
cls.poll_message_set(str(e))
|
||||
return False
|
||||
@@ -136,15 +131,13 @@ class PskExportOperator(Operator, ExportHelper):
|
||||
|
||||
def execute(self, context):
|
||||
pg = context.scene.psk_export
|
||||
builder = PskBuilder()
|
||||
options = PskBuilderOptions()
|
||||
options = PskBuildOptions()
|
||||
options.bone_filter_mode = pg.bone_filter_mode
|
||||
options.bone_group_indices = [x.index for x in pg.bone_group_list if x.is_selected]
|
||||
options.use_raw_mesh_data = pg.use_raw_mesh_data
|
||||
try:
|
||||
psk = builder.build(context, options)
|
||||
exporter = PskExporter(psk)
|
||||
exporter.export(self.filepath)
|
||||
psk = build_psk(context, options)
|
||||
export_psk(psk, self.filepath)
|
||||
except RuntimeError as e:
|
||||
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||
return {'CANCELLED'}
|
||||
|
||||
@@ -12,7 +12,7 @@ from bpy_extras.io_utils import ImportHelper
|
||||
from mathutils import Quaternion, Vector, Matrix
|
||||
|
||||
from .data import Psk
|
||||
from .reader import PskReader
|
||||
from .reader import read_psk
|
||||
from ..helpers import rgb_to_srgb
|
||||
|
||||
|
||||
@@ -26,209 +26,205 @@ class PskImportOptions(object):
|
||||
self.bone_length = 1.0
|
||||
|
||||
|
||||
class PskImporter(object):
|
||||
def __init__(self):
|
||||
def import_psk(psk: Psk, context, options: PskImportOptions):
|
||||
# ARMATURE
|
||||
armature_data = bpy.data.armatures.new(options.name)
|
||||
armature_object = bpy.data.objects.new(options.name, armature_data)
|
||||
armature_object.show_in_front = True
|
||||
|
||||
context.scene.collection.objects.link(armature_object)
|
||||
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except:
|
||||
pass
|
||||
|
||||
def import_psk(self, psk: Psk, context, options: PskImportOptions):
|
||||
# ARMATURE
|
||||
armature_data = bpy.data.armatures.new(options.name)
|
||||
armature_object = bpy.data.objects.new(options.name, armature_data)
|
||||
armature_object.show_in_front = True
|
||||
armature_object.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = armature_object
|
||||
|
||||
context.scene.collection.objects.link(armature_object)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# Intermediate bone type for the purpose of construction.
|
||||
class ImportBone(object):
|
||||
def __init__(self, index: int, psk_bone: Psk.Bone):
|
||||
self.index: int = index
|
||||
self.psk_bone: Psk.Bone = psk_bone
|
||||
self.parent: Optional[ImportBone] = None
|
||||
self.local_rotation: Quaternion = Quaternion()
|
||||
self.local_translation: Vector = Vector()
|
||||
self.world_rotation_matrix: Matrix = Matrix()
|
||||
self.world_matrix: Matrix = Matrix()
|
||||
self.vertex_group = None
|
||||
self.orig_quat: Quaternion = Quaternion()
|
||||
self.orig_loc: Vector = Vector()
|
||||
self.post_quat: Quaternion = Quaternion()
|
||||
|
||||
import_bones = []
|
||||
|
||||
for bone_index, psk_bone in enumerate(psk.bones):
|
||||
import_bone = ImportBone(bone_index, psk_bone)
|
||||
psk_bone.parent_index = max(0, psk_bone.parent_index)
|
||||
import_bone.local_rotation = Quaternion(tuple(psk_bone.rotation))
|
||||
import_bone.local_translation = Vector(tuple(psk_bone.location))
|
||||
if psk_bone.parent_index == 0 and bone_index == 0:
|
||||
import_bone.world_rotation_matrix = import_bone.local_rotation.to_matrix()
|
||||
import_bone.world_matrix = Matrix.Translation(import_bone.local_translation)
|
||||
import_bones.append(import_bone)
|
||||
|
||||
for bone_index, bone in enumerate(import_bones):
|
||||
if bone.psk_bone.parent_index == 0 and bone_index == 0:
|
||||
continue
|
||||
parent = import_bones[bone.psk_bone.parent_index]
|
||||
bone.parent = parent
|
||||
bone.world_matrix = parent.world_rotation_matrix.to_4x4()
|
||||
translation = bone.local_translation.copy()
|
||||
translation.rotate(parent.world_rotation_matrix)
|
||||
bone.world_matrix.translation = parent.world_matrix.translation + translation
|
||||
bone.world_rotation_matrix = bone.local_rotation.conjugated().to_matrix()
|
||||
bone.world_rotation_matrix.rotate(parent.world_rotation_matrix)
|
||||
|
||||
for import_bone in import_bones:
|
||||
bone_name = import_bone.psk_bone.name.decode('utf-8')
|
||||
edit_bone = armature_data.edit_bones.new(bone_name)
|
||||
|
||||
if import_bone.parent is not None:
|
||||
edit_bone.parent = armature_data.edit_bones[import_bone.psk_bone.parent_index]
|
||||
else:
|
||||
import_bone.local_rotation.conjugate()
|
||||
|
||||
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()
|
||||
edit_bone_matrix.translation = import_bone.world_matrix.translation
|
||||
edit_bone.matrix = edit_bone_matrix
|
||||
|
||||
# Store bind pose information in the bone's custom properties.
|
||||
# This information is used when importing animations from PSA files.
|
||||
edit_bone['orig_quat'] = import_bone.local_rotation
|
||||
edit_bone['orig_loc'] = import_bone.local_translation
|
||||
edit_bone['post_quat'] = import_bone.local_rotation.conjugated()
|
||||
|
||||
# MESH
|
||||
mesh_data = bpy.data.meshes.new(options.name)
|
||||
mesh_object = bpy.data.objects.new(options.name, mesh_data)
|
||||
|
||||
# MATERIALS
|
||||
for material in psk.materials:
|
||||
# TODO: re-use of materials should be an option
|
||||
bpy_material = bpy.data.materials.new(material.name.decode('utf-8'))
|
||||
mesh_data.materials.append(bpy_material)
|
||||
|
||||
bm = bmesh.new()
|
||||
|
||||
# VERTICES
|
||||
for point in psk.points:
|
||||
bm.verts.new(tuple(point))
|
||||
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
degenerate_face_indices = set()
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
point_indices = [bm.verts[psk.wedges[i].point_index] for i in reversed(face.wedge_indices)]
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except:
|
||||
pass
|
||||
bm_face = bm.faces.new(point_indices)
|
||||
bm_face.material_index = face.material_index
|
||||
except ValueError:
|
||||
degenerate_face_indices.add(face_index)
|
||||
|
||||
armature_object.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = armature_object
|
||||
if len(degenerate_face_indices) > 0:
|
||||
print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).')
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bm.to_mesh(mesh_data)
|
||||
|
||||
# Intermediate bone type for the purpose of construction.
|
||||
class ImportBone(object):
|
||||
def __init__(self, index: int, psk_bone: Psk.Bone):
|
||||
self.index: int = index
|
||||
self.psk_bone: Psk.Bone = psk_bone
|
||||
self.parent: Optional[ImportBone] = None
|
||||
self.local_rotation: Quaternion = Quaternion()
|
||||
self.local_translation: Vector = Vector()
|
||||
self.world_rotation_matrix: Matrix = Matrix()
|
||||
self.world_matrix: Matrix = Matrix()
|
||||
self.vertex_group = None
|
||||
self.orig_quat: Quaternion = Quaternion()
|
||||
self.orig_loc: Vector = Vector()
|
||||
self.post_quat: Quaternion = Quaternion()
|
||||
# TEXTURE COORDINATES
|
||||
data_index = 0
|
||||
uv_layer = mesh_data.uv_layers.new(name='VTXW0000')
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
if face_index in degenerate_face_indices:
|
||||
continue
|
||||
face_wedges = [psk.wedges[i] for i in reversed(face.wedge_indices)]
|
||||
for wedge in face_wedges:
|
||||
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
||||
data_index += 1
|
||||
|
||||
import_bones = []
|
||||
# EXTRA UVS
|
||||
if psk.has_extra_uvs and options.should_import_extra_uvs:
|
||||
extra_uv_channel_count = int(len(psk.extra_uvs) / len(psk.wedges))
|
||||
wedge_index_offset = 0
|
||||
for extra_uv_index in range(extra_uv_channel_count):
|
||||
data_index = 0
|
||||
uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}')
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
if face_index in degenerate_face_indices:
|
||||
continue
|
||||
for wedge_index in reversed(face.wedge_indices):
|
||||
u, v = psk.extra_uvs[wedge_index_offset + wedge_index]
|
||||
uv_layer.data[data_index].uv = u, 1.0 - v
|
||||
data_index += 1
|
||||
wedge_index_offset += len(psk.wedges)
|
||||
|
||||
for bone_index, psk_bone in enumerate(psk.bones):
|
||||
import_bone = ImportBone(bone_index, psk_bone)
|
||||
psk_bone.parent_index = max(0, psk_bone.parent_index)
|
||||
import_bone.local_rotation = Quaternion(tuple(psk_bone.rotation))
|
||||
import_bone.local_translation = Vector(tuple(psk_bone.location))
|
||||
if psk_bone.parent_index == 0 and bone_index == 0:
|
||||
import_bone.world_rotation_matrix = import_bone.local_rotation.to_matrix()
|
||||
import_bone.world_matrix = Matrix.Translation(import_bone.local_translation)
|
||||
import_bones.append(import_bone)
|
||||
# VERTEX COLORS
|
||||
if psk.has_vertex_colors and options.should_import_vertex_colors:
|
||||
size = (len(psk.points), 4)
|
||||
vertex_colors = np.full(size, inf)
|
||||
vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR')
|
||||
ambiguous_vertex_color_point_indices = []
|
||||
|
||||
for bone_index, bone in enumerate(import_bones):
|
||||
if bone.psk_bone.parent_index == 0 and bone_index == 0:
|
||||
continue
|
||||
parent = import_bones[bone.psk_bone.parent_index]
|
||||
bone.parent = parent
|
||||
bone.world_matrix = parent.world_rotation_matrix.to_4x4()
|
||||
translation = bone.local_translation.copy()
|
||||
translation.rotate(parent.world_rotation_matrix)
|
||||
bone.world_matrix.translation = parent.world_matrix.translation + translation
|
||||
bone.world_rotation_matrix = bone.local_rotation.conjugated().to_matrix()
|
||||
bone.world_rotation_matrix.rotate(parent.world_rotation_matrix)
|
||||
|
||||
for import_bone in import_bones:
|
||||
bone_name = import_bone.psk_bone.name.decode('utf-8')
|
||||
edit_bone = armature_data.edit_bones.new(bone_name)
|
||||
|
||||
if import_bone.parent is not None:
|
||||
edit_bone.parent = armature_data.edit_bones[import_bone.psk_bone.parent_index]
|
||||
for wedge_index, wedge in enumerate(psk.wedges):
|
||||
point_index = wedge.point_index
|
||||
psk_vertex_color = psk.vertex_colors[wedge_index].normalized()
|
||||
if vertex_colors[point_index, 0] != inf and tuple(vertex_colors[point_index]) != psk_vertex_color:
|
||||
ambiguous_vertex_color_point_indices.append(point_index)
|
||||
else:
|
||||
import_bone.local_rotation.conjugate()
|
||||
vertex_colors[point_index] = psk_vertex_color
|
||||
|
||||
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()
|
||||
edit_bone_matrix.translation = import_bone.world_matrix.translation
|
||||
edit_bone.matrix = edit_bone_matrix
|
||||
if options.vertex_color_space == 'SRGBA':
|
||||
for i in range(vertex_colors.shape[0]):
|
||||
vertex_colors[i, :3] = tuple(map(lambda x: rgb_to_srgb(x), vertex_colors[i, :3]))
|
||||
|
||||
# Store bind pose information in the bone's custom properties.
|
||||
# This information is used when importing animations from PSA files.
|
||||
edit_bone['orig_quat'] = import_bone.local_rotation
|
||||
edit_bone['orig_loc'] = import_bone.local_translation
|
||||
edit_bone['post_quat'] = import_bone.local_rotation.conjugated()
|
||||
for loop_index, loop in enumerate(mesh_data.loops):
|
||||
vertex_color = vertex_colors[loop.vertex_index]
|
||||
if vertex_color is not None:
|
||||
vertex_color_data.data[loop_index].color = vertex_color
|
||||
else:
|
||||
vertex_color_data.data[loop_index].color = 1.0, 1.0, 1.0, 1.0
|
||||
|
||||
# MESH
|
||||
mesh_data = bpy.data.meshes.new(options.name)
|
||||
mesh_object = bpy.data.objects.new(options.name, mesh_data)
|
||||
if len(ambiguous_vertex_color_point_indices) > 0:
|
||||
print(f'WARNING: {len(ambiguous_vertex_color_point_indices)} vertex(es) with ambiguous vertex colors.')
|
||||
|
||||
# MATERIALS
|
||||
for material in psk.materials:
|
||||
# TODO: re-use of materials should be an option
|
||||
bpy_material = bpy.data.materials.new(material.name.decode('utf-8'))
|
||||
mesh_data.materials.append(bpy_material)
|
||||
# VERTEX NORMALS
|
||||
if psk.has_vertex_normals and options.should_import_vertex_normals:
|
||||
mesh_data.polygons.foreach_set("use_smooth", [True] * len(mesh_data.polygons))
|
||||
normals = []
|
||||
for vertex_normal in psk.vertex_normals:
|
||||
normals.append(tuple(vertex_normal))
|
||||
mesh_data.normals_split_custom_set_from_vertices(normals)
|
||||
mesh_data.use_auto_smooth = True
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.normal_update()
|
||||
bm.free()
|
||||
|
||||
# VERTICES
|
||||
for point in psk.points:
|
||||
bm.verts.new(tuple(point))
|
||||
# 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'))
|
||||
|
||||
bm.verts.ensure_lookup_table()
|
||||
for weight in psk.weights:
|
||||
import_bones[weight.bone_index].vertex_group.add((weight.point_index,), weight.weight, 'ADD')
|
||||
|
||||
degenerate_face_indices = set()
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
point_indices = [bm.verts[psk.wedges[i].point_index] for i in reversed(face.wedge_indices)]
|
||||
try:
|
||||
bm_face = bm.faces.new(point_indices)
|
||||
bm_face.material_index = face.material_index
|
||||
except ValueError:
|
||||
degenerate_face_indices.add(face_index)
|
||||
# Add armature modifier to our mesh object.
|
||||
armature_modifier = mesh_object.modifiers.new(name='Armature', type='ARMATURE')
|
||||
armature_modifier.object = armature_object
|
||||
mesh_object.parent = armature_object
|
||||
|
||||
if len(degenerate_face_indices) > 0:
|
||||
print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).')
|
||||
context.scene.collection.objects.link(mesh_object)
|
||||
|
||||
bm.to_mesh(mesh_data)
|
||||
|
||||
# TEXTURE COORDINATES
|
||||
data_index = 0
|
||||
uv_layer = mesh_data.uv_layers.new(name='VTXW0000')
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
if face_index in degenerate_face_indices:
|
||||
continue
|
||||
face_wedges = [psk.wedges[i] for i in reversed(face.wedge_indices)]
|
||||
for wedge in face_wedges:
|
||||
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
||||
data_index += 1
|
||||
|
||||
# EXTRA UVS
|
||||
if psk.has_extra_uvs and options.should_import_extra_uvs:
|
||||
extra_uv_channel_count = int(len(psk.extra_uvs) / len(psk.wedges))
|
||||
wedge_index_offset = 0
|
||||
for extra_uv_index in range(extra_uv_channel_count):
|
||||
data_index = 0
|
||||
uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}')
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
if face_index in degenerate_face_indices:
|
||||
continue
|
||||
for wedge_index in reversed(face.wedge_indices):
|
||||
u, v = psk.extra_uvs[wedge_index_offset + wedge_index]
|
||||
uv_layer.data[data_index].uv = u, 1.0 - v
|
||||
data_index += 1
|
||||
wedge_index_offset += len(psk.wedges)
|
||||
|
||||
# VERTEX COLORS
|
||||
if psk.has_vertex_colors and options.should_import_vertex_colors:
|
||||
size = (len(psk.points), 4)
|
||||
vertex_colors = np.full(size, inf)
|
||||
vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR')
|
||||
ambiguous_vertex_color_point_indices = []
|
||||
|
||||
for wedge_index, wedge in enumerate(psk.wedges):
|
||||
point_index = wedge.point_index
|
||||
psk_vertex_color = psk.vertex_colors[wedge_index].normalized()
|
||||
if vertex_colors[point_index, 0] != inf and tuple(vertex_colors[point_index]) != psk_vertex_color:
|
||||
ambiguous_vertex_color_point_indices.append(point_index)
|
||||
else:
|
||||
vertex_colors[point_index] = psk_vertex_color
|
||||
|
||||
if options.vertex_color_space == 'SRGBA':
|
||||
for i in range(vertex_colors.shape[0]):
|
||||
vertex_colors[i, :3] = tuple(map(lambda x: rgb_to_srgb(x), vertex_colors[i, :3]))
|
||||
|
||||
for loop_index, loop in enumerate(mesh_data.loops):
|
||||
vertex_color = vertex_colors[loop.vertex_index]
|
||||
if vertex_color is not None:
|
||||
vertex_color_data.data[loop_index].color = vertex_color
|
||||
else:
|
||||
vertex_color_data.data[loop_index].color = 1.0, 1.0, 1.0, 1.0
|
||||
|
||||
if len(ambiguous_vertex_color_point_indices) > 0:
|
||||
print(f'WARNING: {len(ambiguous_vertex_color_point_indices)} vertex(es) with ambiguous vertex colors.')
|
||||
|
||||
# VERTEX NORMALS
|
||||
if psk.has_vertex_normals and options.should_import_vertex_normals:
|
||||
mesh_data.polygons.foreach_set("use_smooth", [True] * len(mesh_data.polygons))
|
||||
normals = []
|
||||
for vertex_normal in psk.vertex_normals:
|
||||
normals.append(tuple(vertex_normal))
|
||||
mesh_data.normals_split_custom_set_from_vertices(normals)
|
||||
mesh_data.use_auto_smooth = True
|
||||
|
||||
bm.normal_update()
|
||||
bm.free()
|
||||
|
||||
# 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'))
|
||||
|
||||
for weight in psk.weights:
|
||||
import_bones[weight.bone_index].vertex_group.add((weight.point_index,), weight.weight, 'ADD')
|
||||
|
||||
# Add armature modifier to our mesh object.
|
||||
armature_modifier = mesh_object.modifiers.new(name='Armature', type='ARMATURE')
|
||||
armature_modifier.object = armature_object
|
||||
mesh_object.parent = armature_object
|
||||
|
||||
context.scene.collection.objects.link(mesh_object)
|
||||
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class PskImportPropertyGroup(PropertyGroup):
|
||||
@@ -286,8 +282,9 @@ class PskImportOperator(Operator, ImportHelper):
|
||||
|
||||
def execute(self, context):
|
||||
pg = context.scene.psk_import
|
||||
reader = PskReader()
|
||||
psk = reader.read(self.filepath)
|
||||
|
||||
psk = read_psk(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
|
||||
@@ -295,7 +292,9 @@ class PskImportOperator(Operator, ImportHelper):
|
||||
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)
|
||||
|
||||
import_psk(psk, context, options)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -3,53 +3,48 @@ import ctypes
|
||||
from .data import *
|
||||
|
||||
|
||||
class PskReader(object):
|
||||
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 __init__(self):
|
||||
pass
|
||||
|
||||
@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, path) -> Psk:
|
||||
psk = Psk()
|
||||
with open(path, 'rb') as fp:
|
||||
while fp.read(1):
|
||||
fp.seek(-1, 1)
|
||||
section = Section.from_buffer_copy(fp.read(ctypes.sizeof(Section)))
|
||||
if section.name == b'ACTRHEAD':
|
||||
pass
|
||||
elif section.name == b'PNTS0000':
|
||||
PskReader.read_types(fp, Vector3, section, psk.points)
|
||||
elif section.name == b'VTXW0000':
|
||||
if section.data_size == ctypes.sizeof(Psk.Wedge16):
|
||||
PskReader.read_types(fp, Psk.Wedge16, section, psk.wedges)
|
||||
elif section.data_size == ctypes.sizeof(Psk.Wedge32):
|
||||
PskReader.read_types(fp, Psk.Wedge32, section, psk.wedges)
|
||||
else:
|
||||
raise RuntimeError('Unrecognized wedge format')
|
||||
elif section.name == b'FACE0000':
|
||||
PskReader.read_types(fp, Psk.Face, section, psk.faces)
|
||||
elif section.name == b'MATT0000':
|
||||
PskReader.read_types(fp, Psk.Material, section, psk.materials)
|
||||
elif section.name == b'REFSKELT':
|
||||
PskReader.read_types(fp, Psk.Bone, section, psk.bones)
|
||||
elif section.name == b'RAWWEIGHTS':
|
||||
PskReader.read_types(fp, Psk.Weight, section, psk.weights)
|
||||
elif section.name == b'FACE3200':
|
||||
PskReader.read_types(fp, Psk.Face32, section, psk.faces)
|
||||
elif section.name == b'VERTEXCOLOR':
|
||||
PskReader.read_types(fp, Color, section, psk.vertex_colors)
|
||||
elif section.name.startswith(b'EXTRAUVS'):
|
||||
PskReader.read_types(fp, Vector2, section, psk.extra_uvs)
|
||||
elif section.name == b'VTXNORMS':
|
||||
PskReader.read_types(fp, Vector3, section, psk.vertex_normals)
|
||||
def read_psk(path) -> Psk:
|
||||
psk = Psk()
|
||||
with open(path, 'rb') as fp:
|
||||
while fp.read(1):
|
||||
fp.seek(-1, 1)
|
||||
section = Section.from_buffer_copy(fp.read(ctypes.sizeof(Section)))
|
||||
if section.name == b'ACTRHEAD':
|
||||
pass
|
||||
elif section.name == b'PNTS0000':
|
||||
_read_types(fp, Vector3, section, psk.points)
|
||||
elif section.name == b'VTXW0000':
|
||||
if section.data_size == ctypes.sizeof(Psk.Wedge16):
|
||||
_read_types(fp, Psk.Wedge16, section, psk.wedges)
|
||||
elif section.data_size == ctypes.sizeof(Psk.Wedge32):
|
||||
_read_types(fp, Psk.Wedge32, section, psk.wedges)
|
||||
else:
|
||||
raise RuntimeError(f'Unrecognized section "{section.name} at position {15:fp.tell()}"')
|
||||
return psk
|
||||
raise RuntimeError('Unrecognized wedge format')
|
||||
elif section.name == b'FACE0000':
|
||||
_read_types(fp, Psk.Face, section, psk.faces)
|
||||
elif section.name == b'MATT0000':
|
||||
_read_types(fp, Psk.Material, section, psk.materials)
|
||||
elif section.name == b'REFSKELT':
|
||||
_read_types(fp, Psk.Bone, section, psk.bones)
|
||||
elif section.name == b'RAWWEIGHTS':
|
||||
_read_types(fp, Psk.Weight, section, psk.weights)
|
||||
elif section.name == b'FACE3200':
|
||||
_read_types(fp, Psk.Face32, section, psk.faces)
|
||||
elif section.name == b'VERTEXCOLOR':
|
||||
_read_types(fp, Color, section, psk.vertex_colors)
|
||||
elif section.name.startswith(b'EXTRAUVS'):
|
||||
_read_types(fp, Vector2, section, psk.extra_uvs)
|
||||
elif section.name == b'VTXNORMS':
|
||||
_read_types(fp, Vector3, section, psk.vertex_normals)
|
||||
else:
|
||||
raise RuntimeError(f'Unrecognized section "{section.name} at position {15:fp.tell()}"')
|
||||
return psk
|
||||
|
||||
Reference in New Issue
Block a user