Copy over all line-us plotting code from fa9dbf2120

There was a problem with origami which should probably be solved by narrowing the focus of the git Line-us git repository
This commit is contained in:
heyarne 2021-06-03 19:27:18 +02:00
commit 46fc472a40
4 changed files with 234 additions and 0 deletions

View file

@ -0,0 +1,73 @@
(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,79 @@
(ns heyarne.line-us.edge-detection
(:require [clojure.set :as set]
[opencv4.core :as cv]
[opencv4.colors.rgb :as rgb]
[opencv4.utils :as u])
(:import [org.opencv.core Point Rect]))
(def img (-> (u/mat-from-url "https://raw.githubusercontent.com/hellonico/origami/master/doc/cat_in_bowl.jpeg")
(cv/cvt-color! cv/COLOR_RGB2GRAY)
(u/resize-by 0.2)
(cv/canny! 300.0 100.0 3 true)
(cv/bitwise-not!))) ;; NOTE: The bitwise-not! here is not actually needed
(defn pixels
"Returns a vector of
- all pixels in an org.opencv.core.Mat as a byte-array
- width
- height"
[mat]
(let [out (byte-array (* (.total mat) (.channels mat)))]
(.get mat 0 0 out)
[out (.cols mat) (.rows mat)]))
(defn neighborhood [pixels width height x y]
(for [y-off [-1 0 1]
x-off [-1 0 1]
:let [y' (+ y y-off)
x' (+ x x-off)]
:when (and (< -1 x' width)
(< -1 y' height)
(not= y-off x-off 0))]
[[x' y'] (nth pixels (+ x' (* y' width)))]))
(def shape? zero?)
(defn next-connection
"Returns the first neighbor of a pixel that's part of the same shape"
[pixels width height contour coord]
(let [neighbors (apply neighborhood pixels width height coord)]
(reduce (fn [_ [neighbor-coord v]]
(when (and (shape? v)
(not (contour neighbor-coord)))
(reduced neighbor-coord))) neighbors)))
(defn next-start [contour-pixels found-contours]
(first (remove (apply set/union found-contours) contour-pixels)))
(defn radial-sweep
"Returns a seq of sets, where each set represents one stroke / contour"
[mat]
(let [[pixels width height] (pixels mat)
contour-pixels (for [idx (range (.total mat))
:when (shape? (nth pixels idx))]
[(rem idx width) (quot idx width)])
start (first contour-pixels)]
(loop [coord start
contour #{start}
found-contours []]
(let [next (next-connection pixels width height contour coord)]
(if (nil? next)
contour
;; we're continuing with our current contour
(recur next (conj contour next) found-contours))))))
(defn draw-coords! [mat [x y]]
(cv/circle mat (cv/new-point x y) 1 rgb/green 0 cv/LINE_AA)
mat)
(comment
(u/show img)
(radial-sweep img)
(let [out (-> (cv/clone img)
(cv/cvt-color! cv/COLOR_GRAY2RGB))]
(doseq [xy (radial-sweep img)]
(draw-coords! out xy))
(u/show out {:frame {:width (.cols out) :height (.rows out)}})))

View file

@ -0,0 +1,48 @@
(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])
(: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 _))))))
(comment
(require '[thi.ng.geom.circle :as c])
(->
(->gcode (c/circle))
(gcode-seq->str)))

View file

@ -0,0 +1,34 @@
(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)))