Mirrored robot meshes: why a quaternion cannot carry the reflection

A mesh reflected along one axis cannot keep its original geometry and reproduce that reflection using only a unit quaternion and translation. The reflection reverses handedness; a proper rotation preserves it. Before treating an imported object’s transform as a robot pose, check the full linear transform and the mesh coordinates separately. A plausible orientation preview or unchanged dimensions is not enough.

Our AI-operated Blender 5.2.2 LTS study on October 5, 2026 read three authored finite-mesh constructions, their evaluated world matrices and their attached XYZ triads. An independent geometry calculation checked every labeled vertex, edge and face, the matrix rotation conditions, signed volume and handedness. These are virtual geometry and API results. No asset importer, Apply Scale operation, reflected-matrix quaternion extraction, ROS/TF consumer, dynamics or hardware was tested.

ROS REP-103 specifies right-handed coordinate systems and lists quaternions as the preferred rotation representation. That is a coordinate-frame convention. It does not imply that every matrix attached to a mesh is a rotation matrix.

Blender exposes the distinction through an object’s matrix_world and scale. The matrix is a world-space transformation, while scale is a separate three-component property. Its 3 × 3 linear part can therefore contain more than a rigid pose rotation.

Equal edge lengths can hide a reversed shape

Use a deliberately asymmetric tetrahedron with four labeled points in millimeters: A=(0,0,0), B=(120,0,0), C=(0,80,0) and D=(25,15,50). All six edge lengths differ, so the labels identify concrete correspondences rather than interchangeable corners of a symmetric box. Keep their order fixed throughout the comparison.

The original ordered volume is +80,000 mm³. Reflect X and it becomes −80,000 mm³, even though all six distances remain unchanged. The minus sign records orientation; the physical volume is still 80,000 mm³. It is not negative material or mass.

Attach a local XYZ triad to the same object. Under the one-axis reflection, its transformed X and Y cross product points opposite its transformed Z. The triad has become left-handed. Under a proper rotation, the cross-product relationship is preserved.

The actual native scene applies the same (250, −100, 120) mm translation and proper rotation Rz(35°) Ry(−25°) Rx(15°) to every case, acting on column vectors. Those operations change the placement but do not change this sign comparison. An independently calculated diagnostic that forces the reflected frame back to a proper frame while keeping its first two axes changes point D by 100 mm. That candidate no longer reproduces the mirrored geometry. It is an illustrative repair, not a claim about the output of a particular quaternion-conversion function.

Baking the reflection changes where it is stored

There are two ways to construct the same mirrored world vertices in this example. One keeps the original local coordinates and gives the object an X scale of −1. The other reflects the local vertex coordinates and gives the object a proper rotation with unit scale.

Construction Object matrix determinant Fixed-label shape volume
Original shape, proper rotation +1 +80,000 mm³
Original shape, object X reflection −1 −80,000 mm³
Reflected local shape, proper object rotation +1 −80,000 mm³

The final row has a valid proper pose for a different, reflected shape. Its positive determinant does not restore the original asset. This matters when geometry has already been transformed before export: checking only the current object scale can miss how the vertex coordinates changed.

In this study the final row is authored directly as a separate fixture. It does not demonstrate how a particular importer or Blender operator handles baking, normals or export conventions. If the reflected asset is intentional, it can be represented as that asset under a proper pose; the shape change still needs to be kept explicit.

Three native finite tetrahedron comparisons: original proper pose, object X reflection, and reflected local shape with a proper object triad
Native explanatory wire views rebuilt from the three actual evaluated finite tetrahedra and XYZ triads, with the same camera. A–D retain fixed landmark correspondence; RGB arrows follow the object frame. The second and third panels have the same reflected world shape, but only the third has a proper object triad. All six wire edges are shown, so this is not a surface-visibility or normals test. Colors, edge widths, arrows, displaced labels, headings and the legend are drawing aids. Each whole native frame is preserved without cropping or pixel changes.

Here is the comparison without the common translation and rotation, using only Python’s standard library:

# Millimeters. Common translation/proper rotation is omitted: it preserves signs.
vertices = [(0, 0, 0), (120, 0, 0), (0, 80, 0), (25, 15, 50)]
identity = ((1, 0, 0), (0, 1, 0), (0, 0, 1))
mirror_x = ((-1, 0, 0), (0, 1, 0), (0, 0, 1))

def apply(M, p):
    return tuple(sum(M[i][j]*p[j] for j in range(3)) for i in range(3))

def det(M):
    a, b, c = M
    return (a[0]*(b[1]*c[2]-b[2]*c[1])
            - a[1]*(b[0]*c[2]-b[2]*c[0])
            + a[2]*(b[0]*c[1]-b[1]*c[0]))

def proper(M):
    gram = [[sum(M[k][i]*M[k][j] for k in range(3)) for j in range(3)]
            for i in range(3)]
    return det(M) == 1 and gram == [list(row) for row in identity]

for name, pose, shape in (('proper', identity, identity),
                           ('object mirror', mirror_x, identity),
                           ('baked mirror', identity, mirror_x)):
    world = [apply(pose, apply(shape, p)) for p in vertices]
    edges = [tuple(world[k][i]-world[0][i] for i in range(3)) for k in (1, 2, 3)]
    signed_volume = det(edges)/6
    print(name, 'pose det:', det(pose), 'proper pose:', proper(pose),
          'ordered volume mm^3:', signed_volume)

assert proper(((-1, 0, 0), (0, -1, 0), (0, 0, 1)))  # 180-degree Z rotation
assert not proper(((1, 1, 0), (0, 1, 0), (0, 0, 1)))  # det=1 shear

This exact printed script was executed separately from Blender. Its equality checks suit the integer matrices shown. The native audit instead compares actual evaluated vertices and axes within 0.000005 m and matrix entries within 0.000002. Those are numerical study tolerances, not hardware accuracy. The signed volumes from native readback round to the three ±80,000 mm³ values in the table.

Check more than the determinant

For a proper pose rotation, the linear matrix L must satisfy both LᵀL = Identity and det(L) = +1. The first condition excludes scaling and shear; the second excludes reflection. A determinant of +1 alone is insufficient, as the shear assertion above demonstrates. Conversely, two unit negative scale axes can form a proper 180° rotation. The presence of any negative scale component is not by itself a complete diagnosis.

Also avoid treating extracted scale values as a record of which axes were authored as negative. The current Matrix.to_scale() documentation states that it does not return negative axis scales. Our installed Blender 5.2.2 LTS readback differed: the object authored with scale (−1, +1, +1) returned approximately (−1, −1, −1) from matrix_world.to_scale(). The proper and reflected-local-shape cases returned approximately (+1, +1, +1). matrix_world.is_negative was true only for the reflected object transform.

We preserved the initial failed check against the documentation assumption and changed the API audit after inspecting the current official matrix decomposition helper. It normalizes the matrix columns and, on a negative determinant, negates all extracted scale components. That current source snapshot supports the observed convention; it is not a verified source revision for the installed Blender build. The observation is limited to these three constructions. Neither the documentation wording nor this API result recovers the original per-axis authored signs. Keep the full matrix and mesh landmarks.

Reversing triangle winding can change the signed boundary volume and normal orientation, but it does not undo the reflection of the fixed A/B/C/D landmarks. Cross products also carry an extra sign under reflection: axial quantities cannot generally be transformed as if they were ordinary position vectors. This example checks that geometric distinction; it does not convert robot joint axes, angular velocities or torques for a working system.

Use the robot coordinate frames and TF2 guide for transform direction, frame identity and time. This study checks the asset-versus-pose boundary before such a consumer receives a rigid transform. A symmetric or unlabeled mesh may make a reflection hard to spot visually. Preserve identifiable correspondences, distinguish the asset’s shape from its pose, and verify a proper rotation before expecting a quaternion to reproduce the transform.