2019-11-11 10:03:52 +00:00
|
|
|
""" Utilities for operating on msh_model objects. """
|
|
|
|
|
|
|
|
from typing import List
|
|
|
|
from .msh_model import *
|
|
|
|
from .msh_utilities import *
|
2020-10-20 03:18:08 +00:00
|
|
|
import mathutils
|
|
|
|
import math
|
2019-11-11 10:03:52 +00:00
|
|
|
from mathutils import Vector, Matrix
|
|
|
|
|
2020-11-29 23:32:17 +00:00
|
|
|
|
2022-10-06 17:23:13 +00:00
|
|
|
|
|
|
|
# Convert model with geometry to null.
|
|
|
|
# Currently not used, but could be necessary in the future.
|
|
|
|
def make_null(model : Model):
|
|
|
|
model.model_type = ModelType.NULL
|
|
|
|
bone_map = None
|
|
|
|
geometry = None
|
|
|
|
|
|
|
|
|
|
|
|
# I think this is all we need to check for to avoid
|
|
|
|
# common ZE/ZETools crashes...
|
|
|
|
def validate_geometry_segment(segment : GeometrySegment) -> bool:
|
|
|
|
if not segment.positions or not segment.triangle_strips:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-11-29 23:32:17 +00:00
|
|
|
def inject_dummy_data(model : Model):
|
2022-01-13 20:52:30 +00:00
|
|
|
""" Adds a triangle and material to the model (scene root). Needed to export zenasst-compatible skeletons. """
|
2020-11-29 23:32:17 +00:00
|
|
|
model.hidden = True
|
|
|
|
|
|
|
|
dummy_seg = GeometrySegment()
|
|
|
|
dummy_seg.material_name = ""
|
|
|
|
|
|
|
|
dummy_seg.positions = [Vector((0.0,0.1,0.0)), Vector((0.1,0.0,0.0)), Vector((0.0,0.0,0.1))]
|
|
|
|
dummy_seg.normals = [Vector((0.0,1.0,0.0)), Vector((1.0,0.0,0.0)), Vector((0.0,0.0,1.0))]
|
|
|
|
dummy_seg.texcoords = [Vector((0.1,0.1)), Vector((0.2,0.2)), Vector((0.3,0.3))]
|
|
|
|
tri = [[0,1,2]]
|
|
|
|
dummy_seg.triangles = tri
|
|
|
|
dummy_seg.polygons = tri
|
|
|
|
dummy_seg.triangle_strips = tri
|
|
|
|
|
|
|
|
model.geometry = [dummy_seg]
|
|
|
|
model.model_type = ModelType.STATIC
|
|
|
|
|
2022-01-18 20:16:49 +00:00
|
|
|
def convert_vector_space(vec: Vector) -> Vector:
|
2020-11-26 04:10:14 +00:00
|
|
|
return Vector((-vec.x, vec.z, vec.y))
|
|
|
|
|
2022-01-18 20:16:49 +00:00
|
|
|
def convert_scale_space(vec: Vector) -> Vector:
|
|
|
|
return Vector(vec.xzy)
|
|
|
|
|
|
|
|
def convert_rotation_space(quat: Quaternion) -> Quaternion:
|
2020-11-26 04:10:14 +00:00
|
|
|
return Quaternion((-quat.w, quat.x, -quat.z, -quat.y))
|
|
|
|
|
|
|
|
def model_transform_to_matrix(transform: ModelTransform):
|
2022-01-18 20:16:49 +00:00
|
|
|
return Matrix.Translation(convert_vector_space(transform.translation)) @ convert_rotation_space(transform.rotation).to_matrix().to_4x4()
|
2020-11-29 23:32:17 +00:00
|
|
|
|
2019-11-11 10:03:52 +00:00
|
|
|
def scale_segments(scale: Vector, segments: List[GeometrySegment]):
|
|
|
|
""" Scales are positions in the GeometrySegment list. """
|
|
|
|
|
|
|
|
for segment in segments:
|
2019-11-20 06:32:35 +00:00
|
|
|
segment.positions = [mul_vec(pos, scale) for pos in segment.positions]
|
2019-11-11 10:03:52 +00:00
|
|
|
|
|
|
|
def get_model_world_matrix(model: Model, models: List[Model]) -> Matrix:
|
|
|
|
""" Gets a Blender Matrix for transforming the model into world space. """
|
|
|
|
|
|
|
|
transform_stack: List[ModelTransform] = [model.transform]
|
|
|
|
transform_stack.extend((parent.transform for parent in get_model_ancestors(model, models)))
|
|
|
|
|
|
|
|
world_matrix: Matrix = Matrix()
|
|
|
|
|
|
|
|
for transform in reversed(transform_stack):
|
|
|
|
translation_matrix = Matrix.Translation(transform.translation)
|
|
|
|
rotation_matrix = transform.rotation.to_matrix().to_4x4()
|
|
|
|
|
|
|
|
world_matrix = world_matrix @ (translation_matrix @ rotation_matrix)
|
|
|
|
|
|
|
|
return world_matrix
|
|
|
|
|
|
|
|
def sort_by_parent(models: List[Model]) -> List[Model]:
|
|
|
|
""" Sorts a Model list so that models are ordered by their parent.
|
|
|
|
Required for some tools to be able to load .msh files. """
|
|
|
|
|
|
|
|
sorted_models: List[Model] = []
|
|
|
|
|
|
|
|
for root in get_root_models(models):
|
|
|
|
def add_children(model: Model):
|
|
|
|
nonlocal sorted_models
|
|
|
|
|
|
|
|
for child in get_model_children(model, models):
|
|
|
|
sorted_models.append(child)
|
|
|
|
add_children(child)
|
|
|
|
|
|
|
|
sorted_models.append(root)
|
|
|
|
add_children(root)
|
|
|
|
|
|
|
|
return sorted_models
|
|
|
|
|
|
|
|
def reparent_model_roots(models: List[Model]) -> List[Model]:
|
|
|
|
""" Reparents all root models in a list to a new empty node. """
|
|
|
|
|
|
|
|
new_root: Model = Model()
|
|
|
|
new_root.name = get_unique_scene_root_name(models)
|
|
|
|
|
|
|
|
for model in models:
|
|
|
|
if not model.parent:
|
|
|
|
model.parent = new_root.name
|
|
|
|
|
|
|
|
models.insert(0, new_root)
|
|
|
|
|
|
|
|
return models
|
|
|
|
|
|
|
|
def has_multiple_root_models(models: List[Model]) -> bool:
|
|
|
|
""" Checks if a list of Model objects has multiple roots. """
|
|
|
|
|
|
|
|
return sum(1 for root in get_root_models(models)) > 1
|
|
|
|
|
|
|
|
def get_root_models(models: List[Model]) -> Model:
|
|
|
|
""" Generator. Returns all Model objects in a list with no parent. """
|
|
|
|
|
|
|
|
for model in models:
|
|
|
|
if model.parent == "":
|
|
|
|
yield model
|
|
|
|
|
|
|
|
def get_model_children(parent: Model, models: List[Model]) -> Model:
|
|
|
|
""" Generator. Returns all Model objects in a list whose parent is
|
|
|
|
the supplied model. """
|
|
|
|
|
|
|
|
for model in models:
|
|
|
|
if parent.name == model.parent:
|
|
|
|
yield model
|
|
|
|
|
|
|
|
def get_model_ancestors(child: Model, models: List[Model]) -> Model:
|
|
|
|
""" Generator. Yields the parent for a model, then yields the parent's parent,
|
|
|
|
repeating until at the root model. """
|
|
|
|
|
|
|
|
for model in models:
|
|
|
|
if child.parent == model.name:
|
|
|
|
child = model
|
|
|
|
yield model
|
|
|
|
|
|
|
|
def get_unique_scene_root_name(models: List[Model]) -> Model:
|
|
|
|
""" Returns a unique model name of the form of either "SceneRoot" or
|
|
|
|
"SceneRoot{i}". """
|
|
|
|
|
|
|
|
name: str = "SceneRoot"
|
|
|
|
|
|
|
|
if is_model_name_unused(name, models):
|
|
|
|
return name
|
|
|
|
|
|
|
|
for i in range(len(models) + 1):
|
|
|
|
name = f"SceneRoot{i}"
|
|
|
|
|
|
|
|
if is_model_name_unused(name, models):
|
|
|
|
return name
|
|
|
|
|
|
|
|
return name
|
|
|
|
|
|
|
|
def is_model_name_unused(name: str, models: List[Model]) -> bool:
|
|
|
|
""" Checks if there is no Model using a name in a list of models. """
|
|
|
|
|
|
|
|
for model in models:
|
|
|
|
if model.name == name:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
2020-10-16 18:56:03 +00:00
|
|
|
|