ref: d64cf75cf23432793f4daad8e50961b9071bb44e
author: sirjofri <sirjofri@sirjofri.de>
date: Tue Nov 26 16:42:08 EST 2024
adds program
--- /dev/null
+++ b/Readme.md
@@ -1,0 +1,19 @@
+# ARCS - scratch hologram gcode generator
+
+This tool acts as a slicer and generates gcode for scratch holograms.
+
+## Input
+
+The input is a r8g8b8a8 image:
+
+- rgb - "color" values (can be grayscale)
+- alpha - normalized height for the parallax effect
+
+The rgb will be interpreted as grayscale values and dithered. The alpha will be reinterpreted:
+
+- 0.0: fully inside the material
+- 1.0: fully outside the material
+
+## Output
+
+gcode, which can be piped to a 3d printer.
--- /dev/null
+++ b/arcs.c
@@ -1,0 +1,528 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+
+#define INTPATH
+
+void
+usage(void)
+{
+ fprint(2, "usage: [-c] [-x width] [-h toolliftheight] [-z tooldrawheight] %s\n", argv0);
+ exits("usage");
+}
+
+typedef struct Position Position;
+struct Position {
+ float x;
+ float y;
+};
+
+Position
+Pos(float x, float y)
+{
+ Position r;
+ r.x = x;
+ r.y = y;
+ return r;
+}
+
+void
+Psortx(Position *a, Position *b)
+{
+ Position tmp;
+ if (a->x <= b->x)
+ return;
+ tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+void
+Psorty(Position *a, Position *b)
+{
+ Position tmp;
+ if (a->y <= b->y)
+ return;
+ tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+int
+Poseq(Position a, Position b)
+{
+ return a.x == b.x && a.y == b.y;
+}
+
+float
+Pdist(Position a, Position b)
+{
+ return fabs(a.x - b.x) + fabs(a.y - b.y);
+}
+
+void
+Pswiz(Position *a)
+{
+ float t;
+ t = a->x;
+ a->x = a->y;
+ a->y = t;
+}
+
+Biobuf *bout;
+
+float power = 1.0;
+float toolliftheight = 5.;
+float tooldrawheight = 2.;
+float width = 100.; /* default 10cm */
+float radscale = 10.; /* default 1cm */
+Position offset = { 5., 5. }; /* default offset 5mm */
+
+float scale; /* calculated from width */
+
+float travelspeed = 250.; /* ender 5 max speed */
+float drawspeed = 100.; /* guessed */
+float zspeed = 50.; /* guessed */
+
+float minx, maxx;
+float miny, maxy;
+
+int quiet = 0;
+int printinfo = 0;
+int numarcs = 0;
+int numcircles = 0;
+float traveldistance = 0.;
+float drawdistance = 0.;
+float zdistance = 0.;
+
+Position lastpos = {0., 0.};
+
+void
+printheader(void)
+{
+ Bprint(bout, "; generated by arcs\n");
+ Bprint(bout, "M203 X%g Y%g Z%g ; draw speed\n",
+ drawspeed, drawspeed, zspeed);
+ Bprint(bout, "G28 ; home\n");
+ Bprint(bout, "G90 ; absolute positions\n");
+ Bprint(bout, "G01 Z%g\n", toolliftheight);
+}
+
+float
+rgb2val(uchar *abgr)
+{
+ float r, g, b;
+ float rd, m;
+ r = abgr[3] / 256.;
+ r *= 0.2126;
+ g = abgr[2] / 256.;
+ g *= 0.7152;
+ b = abgr[1] / 256.;
+ b *= 0.0722;
+
+ //return r + g + b;
+
+ m = pow(r + g + b, power);
+
+ /* dithering: cheap random dither for now */
+ rd = nrand(256) / 255.;
+
+ return rd > m ? 1. : 0.;
+}
+
+float a2rad(uchar a)
+{
+ return a / 255. * 2. - 1.;
+}
+
+int
+calcarcs(Position o, float r, float y, Position *x1, Position *x2, int inv)
+{
+ float ds;
+ float d;
+
+ if (r < 0.)
+ r *= -1.;
+
+ if (inv)
+ d = -(o.x * o.x) + 2*o.x*y + r*r - y*y;
+ else
+ d = -(o.y * o.y) + 2*o.y*y + r*r - y*y;
+
+ if (d < 0)
+ return 0;
+
+ ds = sqrt(d);
+ if (inv) {
+ x1->x = y;
+ x1->y = o.y + ds;
+ } else {
+ x1->x = o.x + ds;
+ x1->y = y;
+ }
+ if (d == 0) {
+ //fprint(2, "cut: tangent %g, %g (y=%g; d=%g)\n", o.x, o.y, y, d);
+ return 1;
+ }
+
+ if (inv) {
+ x2->x = y;
+ x2->y = o.y - ds;
+ } else {
+ x2->x = o.x - ds;
+ x2->y = y;
+ }
+ //fprint(2, "cut: secant %g, %g (y=%g; d=%g)\n", o.x, o.y, y, d);
+ return 2;
+}
+
+int
+inbox(Position p)
+{
+ if (p.x < minx)
+ return 0;
+ if (p.x > maxx)
+ return 0;
+ if (p.y < miny)
+ return 0;
+ if (p.y > maxy)
+ return 0;
+ return 1;
+}
+
+int
+inboxarc(Position p, Position q)
+{
+ // TODO FIXME: top y border is weird (no new arcs started in that area)
+ if (p.x == q.x && p.y == q.y)
+ return inbox(p);
+ if (p.x >= maxx && q.x >= maxx)
+ if (p.y < q.y)
+ return 0;
+ if (p.x <= minx && q.x <= minx)
+ if (p.y > q.y)
+ return 0;
+ if (p.y >= maxy && q.y >= maxy)
+ if (p.x > q.x)
+ return 0;
+ if (p.y <= miny && q.y <= miny)
+ if (p.x < q.x)
+ return 0;
+ return inbox(p); // needed? otherwise, 1
+}
+
+void
+checknan(Position p, Position o, char *s)
+{
+ if (isNaN(p.x))
+ fprint(2, "%s.x=NaN: %g, %g\n", s, o.x, o.y);
+ if (isNaN(p.y))
+ fprint(2, "%s.y=NaN; %g, %g\n", s, o.x, o.y);
+}
+
+int
+genarcs(Position o, float r, Position *ps)
+{
+ Position x1, x2, x3, x4, y1, y2, y3, y4;
+ int nx1, nx2, ny1, ny2;
+ int i = 0;
+ float or = r;
+ if (r < 0.)
+ r *= -1.;
+
+ nx1 = calcarcs(o, r, maxy, &x1, &x2, 0);
+ nx2 = calcarcs(o, r, miny, &x3, &x4, 0);
+ ny1 = calcarcs(o, r, maxx, &y1, &y2, 1);
+ ny2 = calcarcs(o, r, minx, &y3, &y4, 1);
+
+ switch (nx1) {
+ case 0:
+ x1 = Pos(o.x, o.y + r);
+ case 1:
+ x2 = x1;
+ }
+
+ switch (nx2) {
+ case 0:
+ x3 = Pos(o.x, o.y - r);
+ case 1:
+ x4 = x3;
+ }
+
+ switch (ny1) {
+ case 0:
+ y1 = Pos(o.x + r, o.y);
+ case 1:
+ y2 = y1;
+ }
+
+ switch (ny2) {
+ case 0:
+ y3 = Pos(o.x - r, o.y);
+ case 1:
+ y4 = y3;
+ }
+
+ checknan(x1, o, "x1");
+ checknan(x2, o, "x2");
+ checknan(x3, o, "x3");
+ checknan(x4, o, "x4");
+ checknan(y1, o, "y1");
+ checknan(y2, o, "y2");
+ checknan(y3, o, "y3");
+ checknan(y4, o, "y4");
+
+ Psortx(&x1, &x2);
+ Psortx(&x3, &x4);
+ Psorty(&y2, &y1);
+ Psorty(&y4, &y3);
+
+ /*
+ x1 x2
+ +---x----x---+
+ | |
+ y3 x x y1
+ | . |
+ | |
+ y4 x x y2
+ | |
+ +---x----x---+
+ 0,0 x3 x4
+
+ */
+
+ if (or > 0.) {
+ if (inboxarc(y1, x2)) {
+ ps[i++] = y1;
+ ps[i++] = x2;
+ }
+ if (inboxarc(x1, y3)) {
+ ps[i++] = x1;
+ ps[i++] = y3;
+ }
+ } else {
+ if (inboxarc(y4, x3)) {
+ ps[i++] = y4;
+ ps[i++] = x3;
+ }
+ if (inboxarc(x4, y2)) {
+ ps[i++] = x4;
+ ps[i++] = y2;
+ }
+ }
+
+ return i;
+}
+
+void
+checkpos(Position p)
+{
+ if (p.x < minx)
+ fprint(2, "Position out of range x=%g < %g\n", p.x, minx);
+ if (p.x > maxx)
+ fprint(2, "Position out of range x=%g > %g\n", p.x, maxx);
+ if (p.y < miny)
+ fprint(2, "Position out of range y=%g < %g\n", p.x, miny);
+ if (p.y > maxy)
+ fprint(2, "Position out of range y=%g > %g\n", p.x, maxy);
+}
+
+void
+writearc(int x, int y, uchar *abgr)
+{
+ float radius;
+ Position p;
+ Position points[8];
+ int npoints;
+ float h = rgb2val(abgr);
+
+ if (h < 0.5)
+ return;
+
+ p.x = x * scale + offset.x;
+ p.y = y * scale + offset.y;
+
+ radius = a2rad(abgr[0]) * scale * radscale;
+
+ if (!quiet) {
+ int ispos = 0; /* skip z movement if not needed */
+
+ Bprint(bout, "; PX %d, %d: %d\n", x, y, abgr[0]);
+ // TODO: back and forth mode: for fewer travel paths
+
+ npoints = genarcs(p, radius, points);
+ for (int i = 0; i < npoints; i += 2) {
+ Position from;
+ Position to;
+ Position lookahead;
+ int hasla;
+ from = points[i];
+ to = points[i+1];
+
+ checkpos(from);
+ checkpos(to);
+
+ hasla = i+2 < npoints;
+ if (hasla)
+ lookahead = points[i+2];
+
+ if (!ispos) {
+ Bprint(bout, "G01 X%f Y%f F%f\n", from.x, from.y, travelspeed);
+ Bprint(bout, "G01 Z%f\n", tooldrawheight);
+
+ traveldistance += Pdist(lastpos, from);
+ zdistance += abs(tooldrawheight - toolliftheight);
+ }
+ Bprint(bout, "G03 X%f Y%f R%f ; %d\n", to.x, to.y, radius, i);
+ numarcs++;
+ // TODO: drawdistance
+
+ /* skip z movement if not needed */
+ if (hasla && !Poseq(lookahead, to)) {
+ Bprint(bout, "G01 Z%f\n", toolliftheight);
+ zdistance += abs(tooldrawheight - toolliftheight);
+ }
+
+ ispos = hasla && Poseq(lookahead, to);
+ }
+
+ Bprint(bout, "G01 Z%f\n", toolliftheight);
+ // TODO: only add if needed
+ zdistance += abs(tooldrawheight - toolliftheight);
+ }
+
+ if (!printinfo)
+ return;
+
+ numcircles++;
+ lastpos = p;
+ // TODO: replace with arc calculation
+ drawdistance += 2 * PI * radius;
+}
+
+#pragma varargck type "X" double
+int
+fmttime(Fmt *f)
+{
+ double seconds;
+ int hours;
+ int minutes;
+
+ seconds = va_arg(f->args, double);
+
+ hours = seconds / 3600;
+ seconds -= hours * 3600;
+ minutes = seconds / 60;
+ seconds -= minutes * 60;
+ return fmtprint(f, "%dh %dm %.2fs", hours, minutes, seconds);
+}
+
+float
+printstat(int fd, char *name, float distance, float time)
+{
+ fprint(fd, "%10s: %f (est: %X)\n", name, distance, time);
+ return time;
+}
+
+void
+main(int argc, char **argv)
+{
+ Memimage *img;
+ int readcompressed = 0;
+ uchar *b;
+ float f;
+
+ double t;
+ double loadtime;
+ double proctime;
+
+ ARGBEGIN{
+ case 'c':
+ readcompressed++;
+ break;
+ case 'x':
+ width = atof(EARGF(usage()));
+ break;
+ case 'p':
+ power = atof(EARGF(usage()));
+ break;
+ case 'r':
+ radscale = atof(EARGF(usage()));
+ break;
+ case 'h':
+ toolliftheight = atof(EARGF(usage()));
+ break;
+ case 'z':
+ tooldrawheight = atof(EARGF(usage()));
+ break;
+ case 'i':
+ printinfo++;
+ break;
+ case 'q':
+ quiet++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ t = cputime();
+
+ if (memimageinit())
+ sysfatal("%r");
+
+ img = readcompressed ? creadmemimage(0) : readmemimage(0);
+ if (!img)
+ sysfatal("cannot read memimage: %r");
+
+ loadtime = cputime() - t;
+
+ if (img->chan != RGBA32)
+ sysfatal("unsupported image format");
+
+ bout = Bfdopen(1, OWRITE);
+ if (!bout)
+ sysfatal("%r");
+
+ fmtinstall('X', fmttime);
+
+ scale = width / Dx(img->r);
+
+ if (!quiet)
+ printheader();
+
+ minx = img->r.min.x + offset.x;
+ maxx = img->r.max.x + offset.x;
+ miny = img->r.min.y + offset.y;
+ maxy = img->r.max.y + offset.y;
+
+ t = cputime();
+
+ for (ulong x = img->r.min.x; x < img->r.max.x; x++) {
+ for (ulong y = img->r.min.y; y < img->r.max.y; y++) {
+ b = byteaddr(img, Pt(x, y));
+ writearc(x, y, b);
+ }
+ }
+
+ proctime = cputime() - t;
+
+ if (printinfo) {
+ fprint(2, "statistics:\n\n");
+ f = 0.;
+ fprint(2, "%10s: %X\n", "loadtime", loadtime);
+ fprint(2, "%10s: %X\n", "proctime", proctime);
+ fprint(2, "\n");
+ fprint(2, "%10s: %d\n", "numcircles", numcircles);
+ fprint(2, "%10s: %d\n", "numarcs", numarcs);
+ f += printstat(2, "travel", traveldistance,
+ traveldistance / travelspeed);
+ f += printstat(2, "draw", drawdistance,
+ drawdistance / drawspeed);
+ f += printstat(2, "zdist", zdistance,
+ zdistance / zspeed);
+ fprint(2, "\n%10s: %X\n", "total", f);
+ }
+ exits(nil);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,6 @@
+</$objtype/mkfile
+
+TARG=arcs
+OFILES=arcs.$O
+
+</sys/src/cmd/mkone