Reparenting into a rotated or flipped parent moves the node and leaves it turned the wrong way
SceneGraph.reparentNode keeps a node's place by subtracting the new parent's absolute position
from the node's own, and writes the difference into x and y. Both numbers are page-space
distances, but x and y are an origin in the parent's own axes, and a rotated or flipped parent
turns those axes. The node's rotation, flipX and flipY are not touched either, although they
too are relative to the parent.
So a node that changes parent keeps its place only while both the old and the new parent chains are
unrotated and unflipped. Under a rotated parent it lands somewhere else and is drawn at the wrong
angle. getAbsolutePosition returns the node's own origin mapped through its world matrix, which is
not where x and y put it once the node itself is rotated, so a rotated node moves even when both
parents are unturned.
Every caller goes through this one place: appendChild (so the reparent_node tool), group and
ungroup (so group_nodes and ungroup_node), and _booleanOperation, which moves each operand
into the new node. The editor reaches it too, by its own paths rather than through the plugin API:
editor/structure.ts when it moves a selection or sends nodes back to the page, and editor/undo.ts
when it restores a node's former parent — so an undo of a move under a rotated parent does not put
the node back either. figma-api/components.ts also uses it to gather components into a set.
What happens
- Into a rotated parent. A rectangle at (500, 400) on the page, moved into a frame rotated 30°: it is drawn well away from where it was, and still at 0°, where it should be drawn in the same place and at -30° in its new parent.
- Out of a rotated parent. The same in reverse, and so the move is not reversible: reparenting in and straight back out does not return the node to where it started.
- Ungrouping a rotated group. Its children keep their own angles, which were relative to the group, so the whole arrangement springs back to the page's axes.
- A rotated node between unturned parents. It moves by the difference between its own origin and its unrotated corner.
Reproduction, against OpenPencil's own tools
- Open a document, and with the MCP tools draw a frame 240 by 240 at (100, 50) with
rotation: 30, and a rectangle 40 by 20 at (500, 400) beside it. - Note the rectangle's four corners on the page (
get_nodegives its box; the canvas shows it). - Call
reparent_nodewith the rectangle and the frame. - The rectangle is drawn in a different place, and unrotated, inside the turned frame.
- Call
reparent_nodeagain, back into the page: it does not return to (500, 400).
group_nodes on two nodes inside that rotated frame, and ungroup_node on a group that has itself
been rotated, show the same thing.
What was expected
A node that changes parent stays where it is drawn: its transform in the new parent's frame is its
world transform with the new parent's taken off, which gives x, y, rotation, flipX and
flipY together. Nothing on the page moves.
Where
packages/scene-graph/src/index.ts,SceneGraph.reparentNode, lines 496-527 ate2de247(node.x = absPos.x - newParentAbs.xand the two lines after it).- Its callers:
packages/core/src/figma-api/index.ts—appendChild,group(line 197),ungroup(line 217) and_booleanOperation(line 365);packages/core/src/figma-api/proxy.tslines 259 and 265;packages/core/src/figma-api/components.tsline 462;packages/core/src/editor/structure.tslines 44 and 97; andpackages/core/src/editor/undo.tslines 53 and 60. - The tools that reach it:
packages/core/src/tools/structure/hierarchy.ts—reparent_node,group_nodes,ungroup_node.
Source: open-pencil/open-pencil