shithub: arcs

Download patch

ref: d64cf75cf23432793f4daad8e50961b9071bb44e
author: sirjofri <[email protected]>
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