mirror of
https://github.com/penpot/penpot.git
synced 2026-10-30 22:40:33 +00:00
🐛 Compose child flip flags from the roots during geometry sync
Keep the roots' relative flip state when placing the source subtree. Several small rotations must give the same child flags after a full sync; recomputing one large turn could toggle both axes. Fix F139. The direct regression failed nil/nil to true/true before the fix. Focused geometry and oracle suites pass 20 tests, 88 assertions. Benchmark ratios against Phase 1e: main 10/100/1000 0.57/0.64/0.60; three levels 0.67; chain 0.56; library 0.60; many shapes 0.50; variant switch 0.64. No regression flag. AI-assisted-by: GPT-6
This commit is contained in:
parent
67550ebb5c
commit
90138f14c6
@ -1407,42 +1407,35 @@
|
||||
shape (gsh/move shape delta)]
|
||||
(if-not (ctk/touched-group? dest-root :geometry-group)
|
||||
shape
|
||||
(let [origin-transform (d/nilv (:transform origin-root) (gmt/matrix))
|
||||
dest-transform (d/nilv (:transform dest-root) (gmt/matrix))
|
||||
rel-transform (gmt/multiply dest-transform (gmt/inverse origin-transform))]
|
||||
(if ^boolean (gmt/unit? rel-transform)
|
||||
shape
|
||||
;; The roots differ in rotation/flips: rotate the whole (already moved)
|
||||
;; shape around the dest root center by the roots' relative transform.
|
||||
;; The :rotation attribute delta is fed through the :modifiers path so
|
||||
;; apply-transform keeps it consistent with the resulting matrix.
|
||||
(let [center (grc/rect->center (:selrect dest-root))
|
||||
rel-rotation (mod (- (d/nilv (:rotation dest-root) 0)
|
||||
(d/nilv (:rotation origin-root) 0))
|
||||
360)]
|
||||
(-> shape
|
||||
(assoc-in [:modifiers :rotation] rel-rotation)
|
||||
(gsh/apply-transform (gmt/transform-in center rel-transform))
|
||||
(dissoc :modifiers))))))))
|
||||
(if-let [turn (orient-like origin-root dest-root
|
||||
(grc/rect->center (:selrect dest-root)))]
|
||||
(turn shape)
|
||||
shape))))
|
||||
|
||||
(defn orient-like
|
||||
"Turn the subtree of `head` around `center` to match `target`'s matrix.
|
||||
Flip flags can change during a rotation, so they cannot be applied as
|
||||
separate transforms before that rotation."
|
||||
Compose flip flags with the roots, independent of rotation step size."
|
||||
[head target center]
|
||||
(when-not (and (= (:transform head) (:transform target))
|
||||
(mth/close? (or (:rotation head) 0) (or (:rotation target) 0) 0.01))
|
||||
(let [relative (gmt/multiply (or (:transform target) (gmt/matrix))
|
||||
(or (:transform-inverse head) (gmt/matrix)))
|
||||
transform (gmt/transform-in center relative)
|
||||
turned (gsh/apply-transform head transform)
|
||||
angle (- (or (:rotation target) 0) (or (:rotation turned) 0))]
|
||||
(when (or (not (gmt/unit? relative)) (not (mth/close? (mod angle 360) 0 0.01)))
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(assoc-in [:modifiers :rotation] angle)
|
||||
(gsh/apply-transform transform)
|
||||
(dissoc :modifiers)))))))
|
||||
(let [flip-x? (not= (boolean (:flip-x head)) (boolean (:flip-x target)))
|
||||
flip-y? (not= (boolean (:flip-y head)) (boolean (:flip-y target)))]
|
||||
(when-not (and (= (:transform head) (:transform target))
|
||||
(mth/close? (or (:rotation head) 0) (or (:rotation target) 0) 0.01)
|
||||
(not flip-x?) (not flip-y?))
|
||||
(let [relative (gmt/multiply (or (:transform target) (gmt/matrix))
|
||||
(or (:transform-inverse head) (gmt/matrix)))
|
||||
transform (gmt/transform-in center relative)
|
||||
turned (gsh/apply-transform head transform)
|
||||
angle (- (or (:rotation target) 0) (or (:rotation turned) 0))]
|
||||
(when (or (not (gmt/unit? relative)) (not (mth/close? (mod angle 360) 0 0.01))
|
||||
flip-x? flip-y?)
|
||||
(fn [shape]
|
||||
(let [flip-x (cond-> (:flip-x shape) flip-x? not)
|
||||
flip-y (cond-> (:flip-y shape) flip-y? not)]
|
||||
(-> shape
|
||||
(assoc-in [:modifiers :rotation] angle)
|
||||
(gsh/apply-transform transform)
|
||||
(dissoc :modifiers)
|
||||
(assoc :flip-x flip-x :flip-y flip-y)))))))))
|
||||
|
||||
(defn- maybe-sync
|
||||
[c enabled? done? f]
|
||||
|
||||
@ -200,3 +200,16 @@
|
||||
file (tm/file moved)
|
||||
full (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))]
|
||||
(t/is (= (thn/normalize-file file) (thn/normalize-file full)))))
|
||||
|
||||
(t/deftest full-sync-keeps-child-flips-after-several-small-root-rotations
|
||||
(let [situation (-> (setup/nested-component-with-copy)
|
||||
(r/step (n/rotate :copy-root 45)) r/settle
|
||||
(r/step (n/rotate :copy-root 45)) r/settle
|
||||
(r/step (n/rotate :copy-root 45)) r/settle)
|
||||
file (tm/file situation)
|
||||
head (tm/role-shape situation :copy-instance)
|
||||
child (tm/shape-by-id situation (first (:shapes head)))
|
||||
synced (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))
|
||||
after (get-in (thf/current-page synced) [:objects (:id child)])]
|
||||
(t/is (= (select-keys child [:flip-x :flip-y]) (select-keys after [:flip-x :flip-y])))
|
||||
(t/is (gsh/close-attrs? :points (:points child) (:points after) 0.001))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user