From 37e152bc603efcf9ce547ba66b78a8830c6d78e6 Mon Sep 17 00:00:00 2001 From: arne Date: Fri, 4 Nov 2022 00:33:36 +0100 Subject: [PATCH] Re-add line-us & gcode utils --- src/heyarne/line_us/connection.clj | 74 +++++++++++++++++++++++++++++ src/heyarne/line_us/gcode.clj | 76 ++++++++++++++++++++++++++++++ src/heyarne/line_us/helpers.clj | 35 ++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 src/heyarne/line_us/connection.clj create mode 100644 src/heyarne/line_us/gcode.clj create mode 100644 src/heyarne/line_us/helpers.clj diff --git a/src/heyarne/line_us/connection.clj b/src/heyarne/line_us/connection.clj new file mode 100644 index 0000000..f9b5771 --- /dev/null +++ b/src/heyarne/line_us/connection.clj @@ -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]))) + diff --git a/src/heyarne/line_us/gcode.clj b/src/heyarne/line_us/gcode.clj new file mode 100644 index 0000000..7b45c23 --- /dev/null +++ b/src/heyarne/line_us/gcode.clj @@ -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)]]) + ) diff --git a/src/heyarne/line_us/helpers.clj b/src/heyarne/line_us/helpers.clj new file mode 100644 index 0000000..ea62133 --- /dev/null +++ b/src/heyarne/line_us/helpers.clj @@ -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))) +