Re-add line-us & gcode utils

This commit is contained in:
arne 2022-11-04 00:33:36 +01:00
commit 37e152bc60
3 changed files with 185 additions and 0 deletions

View file

@ -0,0 +1,74 @@
(ns heyarne.line-us.connection
(:require [clojure.java.io :as io])
(:import [java.net Socket]))
;; https://github.com/Line-us/Line-us-Programming/blob/master/Documentation/LineUsDrawingArea.pdf
(def drawing-area
{:x [800 1700]
:y [-900 900]
:z [0 1000]})
(defn connect
[url port]
(Socket. url port))
(defn disconnect [^Socket line-us]
(.close line-us))
(defn read-response [^Socket line-us]
(let [stream (io/input-stream line-us)]
(loop [line []]
(let [c (.read stream)]
(case c
0 (when-not (empty? line)
(apply str (map char line)))
-1 (recur line)
(recur (conj line c)))))))
(defmacro validate-coord
"Throws an exception when a coordinate is outside of the drawable area"
[coord]
(let [[min-v# max-v#] (get drawing-area (keyword (name coord)))]
`(when-not (< ~(dec min-v#) ~coord ~(inc max-v#))
(throw (IllegalArgumentException.
(str ~(name coord) " should be in range [" ~min-v# ", " ~max-v# "] but is " ~coord))))))
(defn- send-command! [^Socket line-us ^String raw-cmd]
;; this is basically taken from the Processing example code and the processing
;; "Client" class
(doto (io/output-stream line-us)
(.write (.getBytes (str raw-cmd "\0")))
(.flush))
;; wait for the response
(let [res (read-response line-us)]
(if-not (re-find #"^(ok|hello)" res)
(throw (Exception. res))
res)))
(defn send-movement!
"Moves the arm to the [x y z] vector that is coord. Coordinates are validated
to be inside the valid drawing area."
[^Socket line-us [x y z :as coords]]
(validate-coord x)
(validate-coord y)
(validate-coord z)
(send-command! line-us (str "G01 X" x " Y" y " Z" z))
coords)
(defn move-home! [^Socket line-us]
(send-movement! line-us [1000 1000 1000]))
(defn- parse-coords [^String raw-coords]
(->>
(re-find #"X:(-?\d+) Y:(\d+) Z:(\d+)" raw-coords)
(rest)
(mapv #(Integer/parseInt % 10))))
(defn move-up! [^Socket line-us]
(let [[x y _] (parse-coords (send-command! line-us "M114"))]
(send-movement! line-us [x y 1000])))
(defn move-down! [^Socket line-us]
(let [[x y _] (parse-coords (send-command! line-us "M114"))]
(send-movement! line-us [x y 200])))

View file

@ -0,0 +1,76 @@
(ns heyarne.line-us.gcode
"Provides functions to move from geometry types provided by thi.ng to gcode
so you can plot them."
(:require [thi.ng.geom.core :as g]
[thi.ng.geom.types]
[thi.ng.geom.vector :as v]
[thi.ng.geom.circle :as c])
(:import [thi.ng.geom.types Line2 LineStrip2 Circle2]))
(defprotocol GCode
"Convert thi.ng.geom types into sequences of GCode to plot them. The GCode
instructions are given as a sequence of vectors, where each vector is given
as [x y z]."
(->gcode [_] [_ r]
"Returns G01 movements on the x, y, and z axis. Z is constantly 1000 in the
current implementation, but this might change in the future. The optional
parameter `r` can be used to adjust the resolution."))
(defn- point-seq->gcode [pts]
(let [vertices pts
[l-x l-y] (last vertices)]
(conj (mapv (fn [[x y]]
[x y 0]) vertices)
[l-x l-y 1000])))
(defn gcode-seq->str [gcode-seq]
(map (fn [[x y z]]
(str "G01 X" x " Y" y " Z" z)) gcode-seq))
(extend-protocol GCode
Line2
(->gcode
([_] (point-seq->gcode (:points _)))
([_ r] (->gcode _)))
LineStrip2
(->gcode
([_] (point-seq->gcode (:points _)))
([_ r] (->gcode _)))
Circle2
(->gcode
([_] (point-seq->gcode (:points (g/as-polygon _))))
([_ r] (point-seq->gcode (:points (g/as-polygon _ r))))))
(defn scene->gcode
"Converts a scene into a sequence of gcode instructions. A scene is an
arbitrarily nested vector of geom instances.
A scene is hiccup like in that each object can be followed by a map of
attributes, which can provide addition information about the preceding
object."
[scene]
(loop [[el & rs] scene
res []]
(cond
(seq? el) (recur (scene->gcode el) res)
(map? el) (recur rs res)
:else (if (seq rs)
(recur rs (conj res (gcode-seq->str (->gcode el))))
(flatten res)))))
(comment
(require '[thi.ng.geom.circle :as c])
;; convert a single element
(->
(->gcode (c/circle) 10)
(gcode-seq->str))
;; convert a tree of elements
(scene->gcode
[(c/circle 10)
[(c/circle [0 20] 10)
(c/circle [0 40] 10)]])
)

View file

@ -0,0 +1,35 @@
(ns heyarne.line-us.helpers
(:require [thi.ng.geom.rect :as rect]
[thi.ng.math.core :as m]))
(defn g01-bounds
"Returns a rect representing the drawing bounds of a sequence of g01 coords"
[g01]
(let [bounds [Double/MAX_VALUE Double/MIN_VALUE Double/MIN_VALUE Double/MAX_VALUE]
[top right bottom left] (reduce (fn [[top right bottom left] [x y _]]
[(min top y) (max right x)
(max bottom y) (min left x)])
bounds
g01)]
(rect/rect [left bottom] [right top])))
(defn rescale
"Returns a new g01 sequence that is proportionally scaled to fit into the
bounding box passed in as the second argument. Assumes that the top left
in g01-seq is at [0 0]."
[g01-seq [top right bottom left]]
(let [s-bounds (g01-bounds g01-seq)
t-bounds (rect/rect [left bottom] [right top])
;; we need to translate the bounding box
[translate-x translate-y] (m/+ (:p s-bounds) (:p t-bounds))
;; and scale it
[s-x s-y] (:p s-bounds)
[t-x t-y] (:p t-bounds)
[s-width s-height] (:size s-bounds)
[t-width t-height] (:size t-bounds)
factor (min (/ (+ t-x t-width) (+ s-x s-width)) (/ (+ t-y t-height) (+ s-y s-height)))]
(map (fn [coord]
(-> (update coord 0 #(+ translate-x (* % factor)))
(update 1 #(+ translate-y (* % factor)))))
g01-seq)))