🐛 Preserve swapped placement when its content main rotates

Prepare the content subtree in the swapped head's orientation before
syncing it. A content main rotation must not replace the orientation of
the position this head occupies in its enclosing component.

The focused regression fails two assertions before the fix. Geometry,
reset, swap and oracle suites pass 77 tests / 312 assertions. The original
generated failure also passes after the fix.

Benchmark vs Phase 1e: main 10/100/1000 0.51/0.57/0.57; levels 0.49;
chain 0.49; library 0.54; many shapes 0.72; switch 0.64. No flags.

AI-assisted-by: GPT-6
This commit is contained in:
alonso.torres 2026-10-23 19:34:54 +02:00
parent 0859d7c1a1
commit 407e14682f
2 changed files with 23 additions and 6 deletions

View File

@ -261,17 +261,21 @@
(recur shape-inst' next-data component')))))))
(defn- main-in-parent-orientation
"Place a main in the orientation of an ordinary instance parent. Such a
parent can survive detach, while the nested copy stays untouched."
"Place a content main in the orientation of a swapped head, or compose
the placement of an ordinary parent that survived detach."
[main-container shape-main container shape-inst reset?]
(let [parent (ctn/get-shape container (:parent-id shape-inst))
main-parent (get-in main-container [:objects (:parent-id shape-main)])
relative (gmt/multiply (or (:transform parent) (gmt/matrix))
(or (:transform-inverse main-parent) (gmt/matrix)))]
(if (or (nil? parent) (and (:shape-ref parent) (not reset?)) (gmt/unit? relative))
swapped? (and (not reset?) (ctk/swapped? shape-inst))
origin (if swapped? shape-main main-parent)
target (if swapped? shape-inst parent)
relative (gmt/multiply (or (:transform target) (gmt/matrix))
(or (:transform-inverse origin) (gmt/matrix)))]
(if (or (nil? target) (and (:shape-ref parent) (not reset?) (not swapped?))
(gmt/unit? relative))
main-container
(let [transform (gmt/transform-in (gsh/shape->center shape-main) relative)
angle (- (or (:rotation parent) 0) (or (:rotation main-parent) 0))
angle (- (or (:rotation target) 0) (or (:rotation origin) 0))
turn #(-> %
(assoc-in [:modifiers :rotation] angle)
(gsh/apply-transform transform)

View File

@ -721,3 +721,16 @@
head (tm/resolve-shape situation :plain-star)]
(t/is (== 40 (:height head)))
(t/is (not (ctk/touched-group? head :geometry-group)))))
(t/deftest swapped-copy-keeps-position-orientation-when-its-content-main-turns
(let [initial (setup-sized)
file (-> initial
(tho/swap-component-in-shape :copy-star :heart)
(transform-subtree :heart-main #(ctm/rotation-modifiers %1 %2 90))
(propagate :heart))
head (ths/get-shape file :copy-star)
child (ths/get-shape-by-id file (first (:shapes head)))]
(t/is (mth/close? 0 (:rotation head) 0.001))
(t/is (mth/close? 0 (:rotation child) 0.001))
(t/is (= #{:swap-group} (:touched head)))
(t/is (nil? (:touched child)))))