001  (ns walkmap.svg
002    "Utility functions for writing stereolithography (STL) files (and possibly,
003    later, other geometry files of interest to us) as scalable vector graphics
004    (SVG)."
005    (:require [clojure.java.io :as io]
006              [clojure.string :as s]
007              [clojure.xml :as x]
008              [dali.io :as neatly-folded-clock]
009              [hiccup.core :refer [html]]
010              [taoensso.timbre :as l :refer [info error spy]]
011              [walkmap.ocean :refer [cull-ocean-facets]]
012              [walkmap.polygon :refer [polygon?]]
013              [walkmap.stl :refer [decode-binary-stl]]
014              [walkmap.vertex :refer [vertex?]]))
015  
016  (def ^:dynamic *preferred-svg-render*
017    "Mainly for debugging dali; switch SVG renderer to use. Expected values:
018    `:dali`, `:hiccup`."
019    :dali)
020  
021  (defn- facet->svg-poly
022    [facet]
023    [:polygon
024     {:points (s/join " " (map #(str (:x %) "," (:y %)) (:vertices facet)))}])
025  
026  (defn- dali-facet->svg-poly
027    [facet]
028    (vec
029      (cons
030        :polygon
031        (map #(vec (list (:x %) (:y %))) (:vertices facet)))))
032  
033  (defn dali-stl->svg
034    "Format this `stl` as SVG for the `dali` renderer on a page with these
035    bounds."
036    [stl minx maxx miny maxy]
037    [:dali/page
038     {:xmlns "http://www.w3.org/2000/svg"
039      :version "1.2"
040      :width (- maxx minx)
041      :height (- maxy miny)
042      :viewBox (s/join " " (map str [minx miny maxx maxy]))}
043     (vec
044       (cons
045         :g
046         (map
047           dali-facet->svg-poly
048           (:facets stl))))])
049  
050  (defn hiccup-stl->svg
051    "Format this `stl` as SVG for the `hiccup` renderer on a page with these
052    bounds."
053    [stl minx maxx miny maxy]
054    [:svg
055     {:xmlns "http://www.w3.org/2000/svg"
056      :version "1.2"
057      :width (- maxx minx)
058      :height (- maxy miny)
059      :viewBox (s/join " " (map str [minx miny maxx maxy]))}
060     (vec
061       (cons
062         :g
063         (map
064           facet->svg-poly
065           (:facets stl))))])
066  
067  (defn stl->svg
068    "Convert this in-memory `stl` structure, as read by `decode-binary-stl`, into
069    an in-memory hiccup representation of SVG structure, and return it."
070    [stl]
071    (let [minx (reduce
072                 min
073                 (map
074                   #(reduce min (map :x (:vertices %)))
075                   (:facets stl)))
076          maxx (reduce
077                 max
078                 (map
079                   #(reduce max (map :x (:vertices %)))
080                   (:facets stl)))
081          miny (reduce
082                 min
083                 (map
084                   #(reduce min (map :y (:vertices %)))
085                   (:facets stl)))
086          maxy (reduce
087                 max
088                 (map
089                   #(reduce max (map :y (:vertices %)))
090                   (:facets stl)))]
091      (l/info "Generating SVG for " *preferred-svg-render* " renderer")
092      (case *preferred-svg-render*
093        :hiccup (hiccup-stl->svg stl minx maxx miny maxy)
094        :dali (dali-stl->svg stl minx maxx miny maxy)
095        (throw (Exception. "Unexpected renderer value: " *preferred-svg-render*)))))
096  
097  (defn binary-stl-file->svg
098    "Given only an `in-filename`, parse the indicated file, expected to be
099    binary STL, and return an equivalent SVG structure. Given both `in-filename`
100    and `out-filename`, as side-effect write the SVG to the indicated output file."
101    ([in-filename]
102     (stl->svg (cull-ocean-facets (decode-binary-stl in-filename))))
103    ([in-filename out-filename]
104     (let [s (binary-stl-file->svg in-filename)]
105       (l/info "Emitting SVG with " *preferred-svg-render* " renderer")
106       (case *preferred-svg-render*
107         :dali (neatly-folded-clock/render-svg s out-filename)
108         :hiccup (spit out-filename (html s))
109         (throw (Exception. "Unexpected renderer value: " *preferred-svg-render*)))
110       s)))