mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
🐛 Keep intersections accurate for short perpendicular lines
An absolute determinant threshold classified short perpendicular lines as parallel. Constraint propagation then displaced text in a rotated nested frame by hundreds of pixels. Compare the determinant relative to both line lengths, retaining the existing parallel-line fallback. The component regression failed on points and x/y before the fix. It now passes, as do direct intersections across sizes and positions, the geometry tests, and existing composable scenarios. Sync benchmark ratios against the 1e baseline (lower quantile): main 10/100/1000 copies 0.47/0.63/0.80; three levels 0.56; chain 0.48; library 0.50; many shapes 0.45; variant switch 0.56. No regression flags. AI-assisted-by: GPT-6
This commit is contained in:
parent
2b31f88797
commit
b86ca2f7fd
@ -387,7 +387,10 @@
|
||||
|
||||
;; Cramer's rule
|
||||
det (- (* a1 b2) (* a2 b1))
|
||||
det (if (mth/almost-zero? det) 0.001 det)
|
||||
;; The determinant scales with both line lengths. Short perpendicular
|
||||
;; lines are not parallel just because their determinant is small.
|
||||
scale (* (mth/hypot a1 b1) (mth/hypot a2 b2))
|
||||
det (if (or (zero? scale) (mth/almost-zero? (/ det scale))) 0.001 det)
|
||||
|
||||
x (/ (- (* b2 c1) (* b1 c2)) det)
|
||||
y (/ (- (* c2 a1) (* c1 a2)) det)]
|
||||
|
||||
@ -270,3 +270,10 @@
|
||||
(t/testing "unrotated shape uses the axis-aligned box"
|
||||
(t/is (true? (gint/has-point? shape (pt 50 40))))
|
||||
(t/is (false? (gint/has-point? shape (pt 200 40)))))))
|
||||
|
||||
(t/deftest short-perpendicular-lines-still-meet-at-their-crossing
|
||||
(doseq [size [0.0005 0.005 1 100]
|
||||
[x y] [[0 0] [200 200] [10000 -10000]]]
|
||||
(let [actual (gint/line-line-intersect (pt (- x size) y) (pt (+ x size) y)
|
||||
(pt x (- y size)) (pt x (+ y size)))]
|
||||
(t/is (gpt/close? (pt x y) actual)))))
|
||||
|
||||
@ -12,16 +12,21 @@
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.geom.modifiers :as gm]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.placement :as clp]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.math :as mth]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.composable.comp.nodes :as n]
|
||||
[app.common.test-helpers.composable.comp.runner :as r]
|
||||
[app.common.test-helpers.composable.core :as tm]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.normalize :as thn]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.test-helpers.texts :as thtx]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[clojure.test :as t]))
|
||||
@ -140,3 +145,29 @@
|
||||
(t/is (nil? (:content-scale (ths/get-shape reset :copy2-root))))
|
||||
(t/is (= (thn/normalize-file changed) (thn/normalize-file undone)))))
|
||||
|
||||
|
||||
(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))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user