mirror of
https://github.com/penpot/penpot.git
synced 2026-10-30 14:30:37 +00:00
🐛 Match component orientation with its relative transform
Apply the relative orientation matrix directly. Large rotations also change flip flags, so applying flips before rotation can add 180 degrees. The text regression fails before this fix at 135 and 225 degrees. Validation: geometry suite, 4 tests / 50 assertions; existing golden cases and worlds, 1098 assertions. No existing golden digest changed. Benchmark lower-quantile ratios against Phase 1e: main 10/100/1000 copies 0.51/0.59/0.58; three levels 0.53; chain 0.61; library 0.53; many shapes 0.54; variant switch 0.70. No regression flag. AI-assisted-by: GPT-6
This commit is contained in:
parent
4f7e9f7e19
commit
705dbd296e
@ -1401,26 +1401,23 @@
|
||||
(dissoc :modifiers))))))))
|
||||
|
||||
(defn orient-like
|
||||
"A function that turns each shape of the subtree of `head` around `center`,
|
||||
so that `head` takes the flips and then the rotation of `target`; nil when
|
||||
they already agree."
|
||||
"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."
|
||||
[head target center]
|
||||
(let [flip-x? (not= (true? (:flip-x head)) (true? (:flip-x target)))
|
||||
flip-y? (not= (true? (:flip-y head)) (true? (:flip-y target)))
|
||||
flip (when (or flip-x? flip-y?)
|
||||
(ctm/resize-modifiers (gpt/point (if flip-x? -1 1) (if flip-y? -1 1)) center))
|
||||
head' (cond-> head (some? flip) (gsh/transform-shape flip))
|
||||
angle (- (d/nilv (:rotation target) 0) (d/nilv (:rotation head') 0))
|
||||
rotate? (not (mth/close? (mod angle 360) 0 0.01))
|
||||
angle (if (> angle 180) (- angle 360) angle)]
|
||||
(when (or (some? flip) rotate?)
|
||||
(fn [shape]
|
||||
(cond-> shape
|
||||
(some? flip)
|
||||
(gsh/transform-shape flip)
|
||||
|
||||
rotate?
|
||||
(as-> shape (gsh/transform-shape shape (ctm/rotation-modifiers shape center angle))))))))
|
||||
(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)))))))
|
||||
|
||||
(defn- maybe-sync
|
||||
[c enabled? done? f]
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
through their constraints (decision (f) of the component contract)."
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.modifiers :as gm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logic.libraries :as cll]
|
||||
@ -147,27 +148,29 @@
|
||||
|
||||
|
||||
(t/deftest rotating-a-copy-with-text-in-a-detached-child-is-already-synced
|
||||
(let [setup (fn []
|
||||
(tm/make-situation
|
||||
(-> (thf/sample-file :file1)
|
||||
(thtx/add-text-component-with-copy [[["Hello world"]]])
|
||||
(ths/update-shape :main-child :grow-type :auto-height)
|
||||
(ths/update-shape :copy-child :grow-type :auto-height)
|
||||
(ths/update-shape :copy-child :touched nil)
|
||||
(tho/add-simple-component :outer :outer-root :outer-child
|
||||
:root-params {:width 100 :height 100})
|
||||
(thc/instantiate-component :outer :outer-copy))))
|
||||
situation (r/run-variant
|
||||
{:setup setup
|
||||
:operation (tm/in-sequence [(n/nest-copy :copy-root :outer-root)
|
||||
(n/detach :copy-root)
|
||||
(n/rotate :outer-copy 45)])})
|
||||
file (tm/file situation)
|
||||
text (first (filter #(= (thi/id :copy-child) (:shape-ref %))
|
||||
(vals (:objects (thf/current-page file)))))
|
||||
full (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))
|
||||
synced (ths/get-shape-by-id full (:id text))]
|
||||
(t/is (some? text))
|
||||
(t/is (gsh/close-attrs? :points (:points text) (:points synced) 0.01))
|
||||
(t/is (mth/close? (:x text) (:x synced) 0.01))
|
||||
(t/is (mth/close? (:y text) (:y synced) 0.01))))
|
||||
(doseq [angle [45 135 225]]
|
||||
(let [setup (fn []
|
||||
(tm/make-situation
|
||||
(-> (thf/sample-file :file1)
|
||||
(thtx/add-text-component-with-copy [[["Hello world"]]])
|
||||
(ths/update-shape :main-child :grow-type :auto-height)
|
||||
(ths/update-shape :copy-child :grow-type :auto-height)
|
||||
(ths/update-shape :copy-child :touched nil)
|
||||
(tho/add-simple-component :outer :outer-root :outer-child
|
||||
:root-params {:width 100 :height 100})
|
||||
(thc/instantiate-component :outer :outer-copy))))
|
||||
situation (r/run-variant
|
||||
{:setup setup
|
||||
:operation (tm/in-sequence [(n/nest-copy :copy-root :outer-root)
|
||||
(n/detach :copy-root)
|
||||
(n/rotate :outer-copy angle)])})
|
||||
file (tm/file situation)
|
||||
text (first (filter #(= (thi/id :copy-child) (:shape-ref %))
|
||||
(vals (:objects (thf/current-page file)))))
|
||||
full (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))
|
||||
synced (ths/get-shape-by-id full (:id text))]
|
||||
(t/is (some? text))
|
||||
(t/is (gmt/close? (:transform text) (:transform synced)))
|
||||
(t/is (gsh/close-attrs? :points (:points text) (:points synced) 0.01))
|
||||
(t/is (mth/close? (:x text) (:x synced) 0.01))
|
||||
(t/is (mth/close? (:y text) (:y synced) 0.01)))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user