diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 8187854..a064870 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -604,7 +604,7 @@ class MeshConverter(_MeshManager): else: mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False) with helpers.TemporaryObject(mesh, bpy.data.meshes.remove): - mesh.transform(bo.matrix_world) + utils.transform_mesh(mesh, bo.matrix_world) return self._export_mesh(bo, mesh) def _export_mesh(self, bo, mesh): diff --git a/korman/exporter/physics.py b/korman/exporter/physics.py index e93bc06..c0303aa 100644 --- a/korman/exporter/physics.py +++ b/korman/exporter/physics.py @@ -78,7 +78,7 @@ class PhysicsConverter: vertices = [hsVector3(i.co.x * scale.x, i.co.y * scale.y, i.co.z * scale.z) for i in mesh.vertices] else: # apply the transform to the physical itself - mesh.transform(mat) + utils.transform_mesh(mesh, mat) mesh.update(calc_tessface=indices) vertices = [hsVector3(*i.co) for i in mesh.vertices] @@ -102,7 +102,7 @@ class PhysicsConverter: mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False) with TemporaryObject(mesh, bpy.data.meshes.remove): # No mass and no emedded xform, so we force worldspace collision. - mesh.transform(bo.matrix_world) + utils.transform_mesh(mesh, bo.matrix_world) mesh.update(calc_tessface=True) if z_coord is None: @@ -283,7 +283,7 @@ class PhysicsConverter: physical.rot = utils.quaternion(mat.to_quaternion()) bmesh.ops.scale(mesh, vec=mat.to_scale(), verts=mesh.verts) else: - mesh.transform(mat) + utils.transform_mesh(mesh, mat) result = bmesh.ops.convex_hull(mesh, input=mesh.verts, use_existing_faces=False) BMVert = bmesh.types.BMVert diff --git a/korman/exporter/utils.py b/korman/exporter/utils.py index cdae5b7..a29a052 100644 --- a/korman/exporter/utils.py +++ b/korman/exporter/utils.py @@ -15,6 +15,7 @@ import bmesh import bpy +import mathutils from typing import Callable, Iterator, Tuple from contextlib import contextmanager @@ -110,3 +111,13 @@ def temporary_mesh_object(source : bpy.types.Object) -> bpy.types.Object: yield obj finally: bpy.data.objects.remove(obj) + +def transform_mesh(mesh: bpy.types.Mesh, matrix: mathutils.Matrix): + # There is a disparity in terms of how negative scaling is displayed in Blender versus how it is + # applied (Ctrl+A) in that the normals are different. Even though negative scaling is evil, we + # prefer to match the visual behavior, not the non-intuitive apply behavior. So, we'll need to + # flip the normals if the scaling is negative. The Blender documentation even "helpfully" warns + # us about this. + mesh.transform(matrix) + if matrix.is_negative: + mesh.flip_normals()