Why a rotated Blender child skews under a scaled parent

A child object can have scale (1,1,1) and still lose a right angle in world space. If its parent stretches X more than Y, turning the child mixes those differently scaled directions. The child’s local rotation remains a rotation, but the complete inherited transform can shear its finite geometry.

Inspect the inherited world edges before changing the child’s rotation. Its local scale fields do not describe every stretch applied by its parent chain.

Consider a synthetic rectangular box measuring 120 × 80 × 40 mm in its own coordinates. Keep its local scale at one and set its parent’s scale to (2,1,1). With the child’s axes aligned to the parent, its world edges are 240, 80 and 40 mm long. The XY corner is still 90°: unequal scaling alone has stretched these aligned edges without making them oblique.

Turn the child 45° about its local Z axis, keeping that parent scale. Now the originally 120 and 80 mm edges become approximately 189.737 and 126.491 mm. At the labeled origin, the two positive XY edges meet at 126.870°, not 90°. A neighboring corner has the supplementary angle. Every scale value is positive; this example does not involve a mirror.

Native aligned and rotated child box wire views with labeled origin O, showing 90 and 126.870 degree XY corners under the same positive parent scale
The actual world boxes viewed along parent Z. O marks the same labeled corner: aligned edges meet at 90°, rotated edges at 126.870°. Wire thickness and angle arcs are drawing aids; depth edges overlap in this view.

The parent stretches the already-turned edges

Follow a local edge into world space. First the child’s rotation points it partly along parent X and partly along parent Y. The parent then doubles its X component while leaving Y unchanged. The two originally perpendicular edges are affected differently because their X components have opposite signs.

Blender’s current parenting implementation combines the parent transform, Parent Inverse and the child’s local transform in that order. The construction explicitly uses an identity Parent Inverse and ordinary object parenting, without constraints or modifiers. An existing scene can have a different Parent Inverse, so the parent’s scale numbers alone do not determine its child’s final geometry.

The Object API distinguishes the local basis before parenting from the world-space matrix. That distinction is the useful debugging step: inspect the inherited world transform or actual world edges as well as the child’s local fields. The cited source snapshot establishes the inspected implementation, not a verified source revision for an installed Blender build.

Two controls help identify what changed. A unit-scale parent preserves the original dimensions and angles at 45°. A uniformly doubled parent produces 240 × 160 × 80 mm edges and preserves the right angle. Uniform scale changes size, so it is still different from a rigid rotation. Nonuniform scale can also preserve right angles when the child axes remain aligned with the scale axes, as the first example shows.

The following standard-library Python reproduces these four prescribed cases. It uses meters internally and reports edge lengths in millimeters. A further proper rotation or translation of the entire parent changes placement without changing these distances or angles.

from math import radians, cos, sin, sqrt, acos, degrees, isclose

# Synthetic local box edges in meters; parent inverse is identity.
size = (.12, .08, .04)
cases = [('unit', (1, 1, 1), 45), ('uniform', (2, 2, 2), 45),
         ('aligned', (2, 1, 1), 0), ('rotated', (2, 1, 1), 45)]
for name, scale, yaw in cases:
    a = radians(yaw)
    # Rotate each child edge first, then scale on the parent's axes.
    ex = (scale[0]*cos(a)*size[0], scale[1]*sin(a)*size[0], 0)
    ey = (-scale[0]*sin(a)*size[1], scale[1]*cos(a)*size[1], 0)
    ez = (0, 0, scale[2]*size[2])
    dot = sum(x*y for x, y in zip(ex, ey))
    lengths = [sqrt(sum(x*x for x in edge)) for edge in (ex, ey, ez)]
    normalized_dot = dot/(lengths[0]*lengths[1])
    angle = degrees(acos(max(-1, min(1, normalized_dot))))
    print(name, 'edges mm:', tuple(round(x*1000, 3) for x in lengths),
          f'XY angle: {angle:.3f} deg; normalized dot: {normalized_dot:.3f}')
    if name == 'rotated':
        assert isclose(normalized_dot, -.6, abs_tol=1e-12)
        assert isclose(angle, 126.86989764584402, abs_tol=1e-10)
    else:
        assert isclose(angle, 90, abs_tol=1e-10)

Making the axes unit length does not restore the right angle

For the 45° case, normalize the two world edge vectors and take their dot product. It is −0.6, whereas perpendicular unit vectors would give zero. Dividing each matrix column by its length therefore leaves the oblique directions intact. It does not turn this inherited map into a rigid rotation.

The native mesh comparison includes a separate reference box at the same world origin using only the authored parent and child rotations. That reference retains the original 120, 80 and 40 mm edges and their right angles. It is a deliberate diagnostic construction, not a claim about what a decomposition function or exporter will return.

The AI-operated Blender 5.2.2 LTS study on October 5, 2026 created four ordinary object hierarchies, each with a parent, a finite child box, an attached 30 mm triad and an unparented rotation-only reference. The independent audit checked the authored parent links, identity Parent Inverse, evaluated world matrices, every labeled vertex, twelve edges, six outward faces and the finite triads. The actual 45° nonuniform case returned the 189.737 and 126.491 mm edges and 126.870° corner above; the other three cases retained right angles.

Its linear Gram matrix, recovered from the world edges divided by their original local lengths, has XY entries of approximately −1.5. Those off-diagonal entries express the loss of perpendicularity; the positive linear determinant, approximately 2, does not rule it out. The complete closed-boundary volume agreed with the scalar triple product of the skew edges. Simply multiplying their three lengths would give the wrong volume.

The parents also had the same proper XYZ rotation of (15,−10,20)° and separate display positions. Neither rigid placement changes these edge metrics. Geometry comparisons used 4 micrometers, triad comparisons used 0.00002 and angle comparisons used 0.002°. These are floating-point consistency checks, not hardware accuracy. No parenting operator, Apply Scale, clear-parent operation, matrix-decomposition correction or exporter was run, and no robot or hardware was tested.

When a child unexpectedly skews, check its parent chain, Parent Inverse and evaluated world edges before deciding that the local rotation itself is wrong. This study diagnoses one static hierarchy with prescribed geometry. It does not establish which scene-editing operation, export format or downstream robot model would preserve the intended shape.

The mirrored-mesh pose study covers a different failure: a reflection can preserve all distances while reversing handedness. Here positive scaling preserves orientation but can change both dimensions and angles. Keep that shape-versus-pose distinction when deciding whether a scene matrix represents a rigid frame.