0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 10:38:13 -05:00

Export/Import and edgecases fixing

This commit is contained in:
alonso.torres 2021-09-27 21:54:47 +02:00
parent 8c25ee7796
commit 75f8e473a5
11 changed files with 240 additions and 129 deletions

View file

@ -278,6 +278,48 @@
(-> file
(update :parent-stack pop))))
(defn add-bool [file data]
(let [frame-id (:current-frame-id file)
name (:name data)
obj (-> {:id (uuid/next)
:type :bool
:name name
:shapes []
:frame-id frame-id}
(merge data)
(check-name file :bool)
(d/without-nils))]
(-> file
(commit-shape obj)
(assoc :last-id (:id obj))
(add-name (:name obj))
(update :parent-stack conjv (:id obj)))))
(defn close-bool [file]
(let [bool-id (-> file :parent-stack peek)
bool (lookup-shape file bool-id)
children (->> bool :shapes (mapv #(lookup-shape file %)))
file
(let [objects (lookup-objects file)
bool' (gsh/update-bool-selrect bool children objects)]
(commit-change
file
{:type :mod-obj
:id bool-id
:operations
[{:type :set :attr :selrect :val (:selrect bool')}
{:type :set :attr :points :val (:points bool')}
{:type :set :attr :x :val (-> bool' :selrect :x)}
{:type :set :attr :y :val (-> bool' :selrect :y)}
{:type :set :attr :width :val (-> bool' :selrect :width)}
{:type :set :attr :height :val (-> bool' :selrect :height)}]}
{:add-container? true}))]
(-> file
(update :parent-stack pop))))
(defn create-shape [file type data]
(let [frame-id (:current-frame-id file)
frame (when-not (= frame-id root-frame)

View file

@ -17,6 +17,9 @@
(def ^:const curve-curve-precision 0.1)
(def ^:const curve-range-precision 2)
(defn s= [a b]
(mth/almost-zero? (- (mth/abs a) b)))
(defn calculate-opposite-handler
"Given a point and its handler, gives the symetric handler"
[point handler]
@ -567,6 +570,34 @@
(mapv #(curve-values curve %)))]
(gpr/points->rect (into [from-p to-p] extremes))))
(defn line-has-point?
"Using the line equation we put the x value and check if matches with
the given Y. If it does the point is inside the line"
[point [from-p to-p :as line]]
(let [{x1 :x y1 :y} from-p
{x2 :x y2 :y} to-p
{px :x py :y} point
m (/ (- y2 y1) (- x2 x1))
vy (+ (* m px) (* (- m) x1) y1)
t (get-line-tval line point)]
;; If x1 = x2 there is no slope, to see if the point is in the line
;; only needs to check the x is the same
(and (or (and (s= x1 x2) (s= px x1))
(s= py vy))
;; This will check if is between both segments
(or (> t 0) (s= t 0))
(or (< t 1) (s= t 1)))))
(defn curve-has-point?
[_point _curve]
;; TODO
#_(or (< (gpt/distance point from-p) 0.01)
(< (gpt/distance point to-p) 0.01))
false
)
(defn line-line-crossing
[[from-p1 to-p1 :as l1] [from-p2 to-p2 :as l2]]
@ -613,26 +644,30 @@
(curve-roots c2' :y)))
(defn ray-line-intersect
[point [from-p to-p :as line]]
(let [ray-line-angle (gpt/angle (gpt/to-vec from-p to-p) (gpt/point 1 0))]
;; If the ray is paralell to the line there will be no crossings
(when (and (> (mth/abs (- ray-line-angle 180)) 0.01)
(> (mth/abs (- ray-line-angle 0)) 0.01))
(let [ray-line [point (gpt/point (inc (:x point)) (:y point))]
[ray-t line-t] (line-line-crossing ray-line line)]
(when (and (some? line-t) (> ray-t 0) (>= line-t 0) (<= line-t 1))
[[(line-values line line-t)
(line-windup line line-t)]])))))
(defn ray-line-intersect
[point line]
;; If the ray is paralell to the line there will be no crossings
(let [ray-line [point (gpt/point (inc (:x point)) (:y point))]
[ray-t line-t] (line-line-crossing ray-line line)]
(when (and (some? line-t)
(> ray-t 0)
(or (> line-t 0) (s= line-t 0))
(or (< line-t 1) (s= line-t 1)))
[[(line-values line line-t)
(line-windup line line-t)]])))
(defn line-line-intersect
[l1 l2]
(let [[l1-t l2-t] (line-line-crossing l1 l2)]
(when (and (some? l1-t) (some? l2-t)
(>= l1-t 0) (<= l1-t 1)
(>= l2-t 0) (<= l2-t 1))
(or (> l1-t 0) (s= l1-t 0))
(or (< l1-t 1) (s= l1-t 1))
(or (> l2-t 0) (s= l2-t 0))
(or (< l2-t 1) (s= l2-t 1)))
[[l1-t] [l2-t]])))
(defn ray-curve-intersect
@ -675,26 +710,7 @@
(defn curve-curve-intersect
[c1 c2]
(letfn [(remove-close-ts [ts]
(loop [current (first ts)
pending (rest ts)
acc nil
result []]
(if (nil? current)
result
(if (and (some? acc)
(< (mth/abs (- current acc)) 0.01))
(recur (first pending)
(rest pending)
acc
result)
(recur (first pending)
(rest pending)
current
(conj result current))))))
(check-range [c1-from c1-to c2-from c2-to]
(letfn [(check-range [c1-from c1-to c2-from c2-to]
(let [r1 (curve-range->rect c1 c1-from c1-to)
r2 (curve-range->rect c2 c2-from c2-to)]
@ -760,14 +776,22 @@
(case (:command cmd)
:line-to (ray-line-intersect point (command->line cmd (command->point prev)))
:curve-to (ray-curve-intersect ray-line (command->bezier cmd (command->point prev)))
#_:else [])))]
#_:else [])))
;; non-zero windup rule
(->> (d/with-prev content)
(mapcat cast-ray)
(map second)
(reduce +)
(not= 0))))
(inside-border? [[cmd prev]]
(case (:command cmd)
:line-to (line-has-point? point (command->line cmd (command->point prev)))
:curve-to (curve-has-point? point (command->bezier cmd (command->point prev)))
#_:else false)
)]
(let [content-with-prev (d/with-prev content)]
(or (->> content-with-prev
(some inside-border?))
(->> content-with-prev
(mapcat cast-ray)
(map second)
(reduce +)
(not= 0))))))
(defn split-line-to
"Given a point and a line-to command will create a two new line-to commands

View file

@ -7,7 +7,8 @@
(ns app.common.geom.shapes.rect
(:require
[app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]))
[app.common.geom.shapes.common :as gco]
[app.common.math :as mth]))
(defn rect->points [{:keys [x y width height]}]
;; (assert (number? x))
@ -71,6 +72,10 @@
:width width
:height height})
(defn s=
[a b]
(mth/almost-zero? (- a b)))
(defn overlaps-rects?
"Check for two rects to overlap. Rects won't overlap only if
one of them is fully to the left or the top"
@ -86,7 +91,7 @@
x2b (+ (:x rect-b) (:width rect-b))
y2b (+ (:y rect-b) (:height rect-b))]
(and (> x2a x1b)
(> x2b x1a)
(> y2a y1b)
(> y2b y1a))))
(and (or (> x2a x1b) (s= x2a x1b))
(or (>= x2b x1a) (s= x2b x1a))
(or (<= y1b y2a) (s= y1b y2a))
(or (<= y1a y2b) (s= y1a y2b)))))

View file

@ -151,7 +151,6 @@
(contains? #{:line-to :curve-to} (:command segment)))
(case (:command segment)
:line-to (let [[p1 q1] (gsp/command->line segment)
[p2 q2] (gsp/command->line other)]
@ -180,7 +179,8 @@
(d/concat
[]
(->> content-a-split (filter #(not (contains-segment? % content-b))))
(->> content-b-split (filter #(not (contains-segment? % content-a))))))
(->> content-b-split (filter #(or (not (contains-segment? % content-a))
(overlap-segment? % content-a-split))))))
(defn create-difference [content-a content-a-split content-b content-b-split]
;; Pick all segments in content-a that are not inside content-b
@ -194,8 +194,8 @@
(->> content-b-split
(reverse)
(mapv reverse-command)
(filter #(contains-segment? % content-a))
(filter #(not (overlap-segment? % content-a-split))))))
(filter #(and (contains-segment? % content-a)
(not (overlap-segment? % content-a-split)))))))
(defn create-intersection [content-a content-a-split content-b content-b-split]
;; Pick all segments in content-a that are inside content-b

View file

@ -8,89 +8,106 @@
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gsp]
[app.common.path.bool :as pb]
[app.common.path.shapes-to-path :as stp]
[app.main.ui.hooks :refer [use-equal-memo]]
[app.main.ui.shapes.export :as use]
[app.main.ui.shapes.path :refer [path-shape]]
[app.util.object :as obj]
[rumext.alpha :as mf]))
(mf/defc debug-bool
{::mf/wrap-props false}
[props]
(let [frame (obj/get props "frame")
shape (obj/get props "shape")
childs (obj/get props "childs")
[content-a content-b]
(mf/use-memo
(mf/deps shape childs)
(fn []
(let [childs (d/mapm #(-> %2 (gsh/translate-to-frame frame) gsh/transform-shape) childs)
[content-a content-b]
(->> (:shapes shape)
(map #(get childs %))
(filter #(not (:hidden %)))
(map #(stp/convert-to-path % childs))
(mapv :content)
(mapv pb/add-previous))]
(pb/content-intersect-split content-a content-b))))]
[:g.debug-bool
[:g.shape-a
[:& path-shape {:shape (-> shape
(assoc :type :path)
(assoc :stroke-color "blue")
(assoc :stroke-opacity 1)
(assoc :stroke-width 0.5)
(assoc :stroke-style :solid)
(dissoc :fill-color :fill-opacity)
(assoc :content content-b))
:frame frame}]
(for [{:keys [x y]} (gsp/content->points content-b)]
[:circle {:cx x
:cy y
:r 2.5
:style {:fill "blue"}}])]
[:g.shape-b
[:& path-shape {:shape (-> shape
(assoc :type :path)
(assoc :stroke-color "red")
(assoc :stroke-opacity 1)
(assoc :stroke-width 0.5)
(assoc :stroke-style :solid)
(dissoc :fill-color :fill-opacity)
(assoc :content content-a))
:frame frame}]
(for [{:keys [x y]} (gsp/content->points content-a)]
[:circle {:cx x
:cy y
:r 1.25
:style {:fill "red"}}])]])
)
(defn bool-shape
[shape-wrapper]
(mf/fnc bool-shape
{::mf/wrap-props false}
[props]
(let [frame (obj/get props "frame")
shape (obj/get props "shape")
childs (obj/get props "childs")
{::mf/wrap-props false}
[props]
(let [frame (obj/get props "frame")
shape (obj/get props "shape")
childs (obj/get props "childs")
childs (use-equal-memo childs)
childs (use-equal-memo childs)
;;[content-a content-b]
;;(mf/use-memo
;; (mf/deps shape childs)
;; (fn []
;; (let [childs (d/mapm #(gsh/transform-shape %2) childs)
;; [content-a content-b]
;; (->> (:shapes shape)
;; (map #(get childs %))
;; (filter #(not (:hidden %)))
;; (map #(stp/convert-to-path % childs))
;; (mapv :content)
;; (mapv pb/add-previous))]
;; (pb/content-intersect-split content-a content-b))))
include-metadata? (mf/use-ctx use/include-metadata-ctx)
;;_ (.log js/console "content-a" (clj->js content-a))
;;_ (.log js/console "content-b" (clj->js content-b))
bool-content
(mf/use-memo
(mf/deps shape childs)
(fn []
(let [childs (d/mapm #(gsh/transform-shape %2) childs)]
(->> (:shapes shape)
(map #(get childs %))
(filter #(not (:hidden %)))
(map #(stp/convert-to-path % childs))
(mapv :content)
(pb/content-bool (:bool-type shape))))))
]
bool-content
(mf/use-memo
(mf/deps shape childs)
(fn []
(let [childs (d/mapm #(-> %2 (gsh/translate-to-frame frame) gsh/transform-shape) childs)]
(->> (:shapes shape)
(map #(get childs %))
(filter #(not (:hidden %)))
(map #(stp/convert-to-path % childs))
(mapv :content)
(pb/content-bool (:bool-type shape))))))]
[:*
[:& shape-wrapper {:shape (-> shape
(assoc :type :path)
(assoc :content bool-content))
:frame frame}]
[:*
[:& path-shape {:shape (assoc shape :content bool-content)}]
(when include-metadata?
[:> "penpot:bool" {}
(for [item (->> (:shapes shape) (mapv #(get childs %)))]
[:& shape-wrapper {:frame frame
:shape item
:key (:id item)}])])
#_[:*
[:g
[:& shape-wrapper {:shape (-> shape
(assoc :type :path)
(assoc :stroke-color "blue")
(assoc :stroke-opacity 1)
(assoc :stroke-width 0.5)
(assoc :stroke-style :solid)
(dissoc :fill-color :fill-opacity)
(assoc :content content-b))
:frame frame}]
(for [{:keys [x y]} (app.common.geom.shapes.path/content->points content-b)]
[:circle {:cx x
:cy y
:r 2.5
:style {:fill "blue"}}])]
[:g
[:& shape-wrapper {:shape (-> shape
(assoc :type :path)
(assoc :stroke-color "red")
(assoc :stroke-opacity 1)
(assoc :stroke-width 0.5)
(assoc :stroke-style :solid)
(dissoc :fill-color :fill-opacity)
(assoc :content content-a))
:frame frame}]
(for [{:keys [x y]} (app.common.geom.shapes.path/content->points content-a)]
[:circle {:cx x
:cy y
:r 1.25
:style {:fill "red"}}])]]])))
#_[:& debug-bool {:frame frame
:shape shape
:childs childs}]])))

View file

@ -64,6 +64,7 @@
text? (= :text (:type shape))
path? (= :path (:type shape))
mask? (and group? (:masked-group? shape))
bool? (= :bool (:type shape))
center (gsh/center-shape shape)]
(-> props
(add! :name)
@ -102,7 +103,10 @@
(add! :content (comp json/encode uuid->string))))
(cond-> mask?
(obj/set! "penpot:masked-group" "true")))))
(obj/set! "penpot:masked-group" "true"))
(cond-> bool?
(add! :bool-type)))))
(defn add-library-refs [props shape]

View file

@ -72,6 +72,7 @@
[:> wrapper-tag wrapper-props
(when include-metadata?
[:& ed/export-data {:shape shape}])
[:defs
[:& defs/svg-defs {:shape shape :render-id render-id}]
[:& filters/filters {:shape shape :filter-id filter-id}]

View file

@ -201,11 +201,12 @@
[:& use/export-page {:options options}]
[:& (mf/provider embed/context) {:value true}
;; Render root shape
[:& shapes/root-shape {:key page-id
:objects objects
:active-frames @active-frames}]]]
[:& (mf/provider use/include-metadata-ctx) {:value true}
[:& (mf/provider embed/context) {:value true}
;; Render root shape
[:& shapes/root-shape {:key page-id
:objects objects
:active-frames @active-frames}]]]]
[:svg.viewport-controls
{:xmlns "http://www.w3.org/2000/svg"

View file

@ -209,6 +209,13 @@
(->> node :content last))]
(merge (add-attrs {} (:attrs svg-node)) node-attrs))
(= type :bool)
(->> node
(:content)
(filter #(= :path (:tag %)))
(map #(:attrs %))
(reduce add-attrs node-attrs))
:else
node-attrs)))
@ -443,6 +450,11 @@
mask?
(assoc :masked-group? true))))
(defn add-bool-data
[props node]
(-> props
(assoc :bool-type (get-meta node :bool-type keyword))))
(defn parse-shadow [node]
{:id (uuid/next)
:style (get-meta node :shadow-type keyword)
@ -706,7 +718,10 @@
(add-image-data type node))
(cond-> (= :text type)
(add-text-data node))))))
(add-text-data node))
(cond-> (= :bool type)
(add-bool-data node))))))
(defn parse-page-data
[node]

View file

@ -81,8 +81,8 @@
last-move (if current-move? point last-move)]
(if (and (not current-move?) (pt= last-move point))
(println (command->string (set-point current last-move)))
(println (command->string current)))
(print (command->string (set-point current last-move)))
(print (command->string current)))
(when (and (not current-move?) (pt= last-move point))
(print "Z"))

View file

@ -202,6 +202,7 @@
(case type
:frame (fb/close-artboard file)
:group (fb/close-group file)
:bool (fb/close-bool file)
:svg-raw (fb/close-svg-raw file)
#_default file)
@ -218,6 +219,7 @@
file (case type
:frame (fb/add-artboard file data)
:group (fb/add-group file data)
:bool (fb/add-bool file data)
:rect (fb/create-rect file data)
:circle (fb/create-circle file data)
:path (fb/create-path file data)