Skip to content

Latest commit

 

History

History
298 lines (249 loc) · 10.4 KB

examples.org

File metadata and controls

298 lines (249 loc) · 10.4 KB

Examples & test trees

All examples defined below will be tangled into the /babel/examples/ subfolder of this project and can be directly loaded into a running REPL, e.g. via (load-file "examples/ex03.clj"). Loading the first example will take a few seconds, since the thi.ng/geom library is quite large, however subsequent calls will take < 1 sec…

;; run all examples
(doseq [i (range 1 6)] (load-file (str "examples/ex0" i ".clj")))

(See index.org for further information how to tangle & build this project)

Each example generates one or more 3D models in binary STL format, which are saved in the /out directory and can then be imported into other 3d applications. If you need a simple mesh viewer, we recommend Meshlab.

Template for namespace setup

(:require
 [thi.ng.morphogen.core :as mg]
 [thi.ng.geom.core :as g]
 [thi.ng.geom.vector :as v :refer [vec3]]
 [thi.ng.geom.aabb :as a])
(:import
 [thi.ng.morphogen.core BoxNode])

Reusable helpers

At some point these will be refactored and moved into core ns…

(defn make-stripes
  "Returns a tree which subdivides form into `n` columns and only
  keeps those for whose index the given predicate returns a truthy
  value. If no predicate is given, `even?` is used by default."
  ([n] (make-stripes even? n))
  ([pred n]
     (mg/subdiv :cols n :out (mapv #(if (pred %) {}) (range n)))))

Extruded aluminium style module

../assets/img/morphogen-ex01.jpg

(ns ex01
  <<require-mg>>)

(def tree
  (let [branch (fn [[dir lpos]]
                 (mg/subdiv-inset
                  :dir :y :inset 0.05
                  :out {lpos (mg/subdiv dir 3 :out {1 nil}) 4 nil}))
        module (mg/subdiv-inset
                :dir :y :inset 0.4
                :out (mapv branch [[:cols 0] [:cols 1] [:slices 2] [:slices 3]]))]
    (mg/subdiv
     :rows 3
     :out [module
           (mg/subdiv
            :rows 3 :out {1 (mg/subdiv :cols 3 :out [nil {} nil])})
           module])))

(mg/save-stl-mesh (mg/seed-box (a/aabb 1 0.5 1)) tree "out/ex01.stl")

Stripy module (heatsink?)

../assets/img/morphogen-ex02.jpg

(ns ex02
  <<require-mg>>)

<<stripes>>

(defn stripes*
  "Similar to `make-stripes`, but replaces the killed off columns with
  connector elements by splitting each in 3x3 rows/slices and only
  keeping the center one. Returns tree of columns as created by
  `make-stripes` and connectors between them."
  [pred n]
  (loop [acc (make-stripes pred n) i (if (= pred even?) 1 0)]
    (if (< i n)
      (recur
       (assoc-in
        acc [:out i]
        (mg/subdiv
         :rows 3 :slices 3 :out {4 {}} :empty? true))
       (+ i 2))
      acc)))

(def tree
  "Main tree. Splits seed form into 3x3 cols/slices, removes center
  and replaces others with striped versions."
  (let [se (stripes* even? 9)
        so (mg/subdiv :rows 2 :out [(stripes* odd? 9)])]
    (mg/subdiv :cols 3 :slices 3 :out [se so se se nil se se so se])))

(mg/save-stl-mesh (mg/seed-box (a/aabb 1 0.2 1)) tree "out/ex02.stl")

Hexagon hemisphere

../assets/img/morphogen-ex03.jpg

The following short code is all what’s needed to generate the mesh shown in the image above: A sphere segment (generated with sphere-lattice-seg) is used as seed shape to create a number of hexagons, which due to the side angles of the seed shape automatically arrange themselves in a spherical constellation. The entire structure is only using the reflection operator. Since the operator tree is context free & distinct from any actual geometry node hierarchy, we can encode various repetitive sub-transformations using simple functions.

(ns ex03
  <<require-mg>>)

(def t
  "Arrangement of 10 hexagons as sequence of nested reflections."
  (let [hex (mg/apply-recursively (mg/reflect :e) 5 [1] 1)
        reflected-hex (mg/reflect :n :out [{} hex])
        inject #(-> hex
                    (assoc-in (mg/child-path [1 1 0]) %)
                    (assoc-in (mg/child-path [1 1 1 1 0]) %))
        seed-clone (mg/reflect :s :out [{} (inject reflected-hex)])]
    (mg/reflect :s :out [(inject seed-clone) (inject reflected-hex)])))

;; apply to sphere lattice segment to form hemisphere
(mg/save-stl-mesh (mg/seed-box (mg/sphere-lattice-seg 6 0.25 0.0955 0.2)) t "out/ex03.stl")

;; apply to circle lattice segment to form flat structure
(mg/save-stl-mesh (mg/seed-box (mg/circle-lattice-seg 6 0.25 0.2)) t "out/ex03-alt.stl")

The example also includes a variation of the same tree applied to a circle lattice segment forming a flat, planar structure instead of a hemisphere. Just uncomment the last line to see its result.

../assets/img/morphogen-ex03-flat.jpg

Hex virus

../assets/img/morphogen-virus.jpg

This structure very nicely demonstrates the potential of the whole morphogenic approach and uses the same hexagonal base elements as the previous example, but with the tree having much deeper nesting to create the additional antenna structures & hollowed skeleton cells, all of which are unfolded & extruded from the 60 individual hexagon line segments. The resulting structure has 51840 faces, 8460 times that the initial seed form/box!

(ns ex04
  (:require
   [thi.ng.morphogen.core :as mg]
   [thi.ng.geom.core :as g]
   [thi.ng.geom.aabb :as a])
  (:import
   [thi.ng.morphogen.core BoxNode]))

(defn hollow-out
  "Returns a tree which applies the `subdiv-inset` operator to a form
  in the given direction and removes the center child or optionally
  injects given :out vector/map of children. If `empty?` is true, all
  children are removed by default (only makes sense if other children
  are given, else it should be false)."
  ([dir inset]
     (hollow-out dir inset false {4 nil}))
  ([dir inset empty? out]
     (mg/subdiv-inset :dir dir :inset inset :out out :empty? empty?)))

(defn antenna-ring
  "Returns tree which extrudes form in given direction (east/west),
  splits it into 3 columns, scales side edges of last to half length
  and then forms ring using last column by recursively reflecting it
  `n` times."
  [dir edge1 edge2 ring-child-id n]
  (let [ring (mg/apply-recursively (mg/reflect :n) n [1] 1)]
    (mg/extrude
     :dir dir :len 0.1
     :out
     [(mg/scale-edge
       edge1 :y
       :out
       [(mg/subdiv
         :cols 3
         :out
         {ring-child-id (mg/scale-edge edge2 :z :out [ring])})])])))

(def antenna-rings
  "The two east/west facing ring modules attached to the tip of the antenna."
  [(antenna-ring :w :ab :bf 0 13)
   (antenna-ring :e :cd :cg 2 13)])

(def antenna-tip
  "The last (thinnest) two antenna segments with rings attached to the lower one."
  (mg/subdiv
   :cols 3
   :out [nil
         (mg/extrude
              :dir :b :len 0.5
              :out [(mg/subdiv
                     :slices 8
                     :out {0 (hollow-out :z 0.01 false {4 (mg/extrude :dir :b :len 1)})
                           1 (mg/subdiv :cols 2 :out antenna-rings)})])
         nil]))

(def antenna-main
  "Main antenna parts without base ."
  (hollow-out
   :z 0.025 true
   {4 (mg/extrude
       :dir :b :len 0.25
       :out [(hollow-out :z 0.025 false {4 antenna-tip})])}))

(defn antenna-module
  "Complete antenna module with configurable base."
  [inset]
  (mg/extrude
   :dir :b :len 0.2
   :out [(hollow-out
          :z inset false
          {4 (mg/subdiv :slices 2 :out {0 antenna-main})})]))

(def tree
  "Main tree. First forms arrangement of 10 hexagons using seed form &
  sequence of nested reflections. Then applies skeletonization and
  attaches antennas to all 60 segments. These are injected using the
  `map-leaves` function which replaces all leaf nodes in the tree with
  a new subtree."
  (let [hex (mg/apply-recursively (mg/reflect :e) 5 [1] 1)
        refl-hex (mg/reflect :n :out [{} hex])
        inject #(-> hex
                    (assoc-in (mg/child-path [1 1 0]) %)
                    (assoc-in (mg/child-path [1 1 1 1 0]) %))
        seed-clone (mg/reflect :s :out {1 (inject refl-hex)})
        inset 0.03
        skeleton (hollow-out
                  :y inset false
                  [(hollow-out :z inset)
                   (mg/subdiv
                    :cols 3
                    :out [(hollow-out :z inset)
                          (antenna-module inset)
                          (hollow-out :z inset)])
                   (hollow-out :x inset)
                   (hollow-out :x inset)])]
    (->> (mg/reflect :s :out [(inject seed-clone) (inject refl-hex)])
         (mg/map-leaves (constantly skeleton)))))

;; apply to sphere lattice segment to form hemisphere
(mg/save-stl-mesh (mg/seed-box (mg/sphere-lattice-seg 6 0.25 0.0955 0.2)) tree "out/ex04.stl")

MIT Selfassembly module

../assets/img/morphogen-ex05.jpg

(ns ex05
  <<require-mg>>)

<<stripes>>

(defn stripes*
  "Similar to `make-stripes`, but replaces the killed off columns with
  connector elements by splitting each with the
  keeping the center one. Returns tree of columns as created by
  `make-stripes` and connectors between them."
  [pred gap-opts n]
  (loop [acc (make-stripes pred n) i (if (= pred even?) 1 0)]
    (if (< i n)
      (recur
       (assoc-in acc [:out i] (apply mg/subdiv gap-opts))
       (+ i 2))
      acc)))

(def tree
  (mg/subdiv
   :cols 3
   :out [(mg/subdiv
          :cols 2 :out [(mg/subdiv-inset :dir :x :inset 0.005 :out {4 nil}) {}])
         (stripes* odd? [:rows 3 :out [nil {} nil]] 19)
         (mg/subdiv
          :cols 2 :out [{} (mg/subdiv-inset :dir :x :inset 0.0055 :out {4 {}} :empty? true)])]))

(mg/save-stl-mesh (mg/seed-box (a/aabb 1 0.2 0.2)) tree "out/ex05.stl")