$ git clone http://gcodetool.ion.nu/gcodetool.git
commit d9df22e382a0d476f71bc4fbd6903b60fc2f5d85
Author: Alicia <...>
Date: Sun Oct 6 21:41:50 2019 +0000
Added a preview subcommand. Not the cleanest code but it's a start.
diff --git a/Makefile b/Makefile
index 78186f8..35a9118 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
PREFIX=/usr
BINDIR=$(PREFIX)/bin
CFLAGS=-Wall -g3
-gcodetool: main.o gcode.o mirror.o move.o info.o
+gcodetool: main.o gcode.o mirror.o move.o info.o preview.o preview_term.o preview_pnm.o
$(CC) $^ -lm -o $@
static: CC+=-static
static: gcodetool
diff --git a/main.c b/main.c
index a82fb5a..e307c5c 100644
--- a/main.c
+++ b/main.c
@@ -22,6 +22,7 @@
extern int gtool_mirror_arg(int argc, char** argv);
extern int gtool_move_arg(int argc, char** argv);
extern int gtool_info_arg(int argc, char** argv);
+extern int gtool_preview_arg(int argc, char** argv);
int main(int argc, char** argv)
{
@@ -37,6 +38,10 @@ int main(int argc, char** argv)
{
return gtool_info_arg(argc, argv);
}
+ else if(argc>2 && !strcmp(argv[1], "preview"))
+ {
+ return gtool_preview_arg(argc, argv);
+ }
#ifdef WIN32 // For lazy open-with
else if(argc>1 && !access(argv[1], R_OK))
{
diff --git a/preview.c b/preview.c
new file mode 100644
index 0000000..5bd393a
--- /dev/null
+++ b/preview.c
@@ -0,0 +1,273 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2019 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include "gcode.h"
+#include "preview.h"
+extern struct img* gtool_preview_term(int width, int height, const char* filename);
+extern struct img* gtool_preview_pnm(int width, int height, const char* filename);
+
+struct line // TODO: Consider non-planar printing?
+{
+ double x[2];
+ double y[2];
+};
+
+struct vectorlayer
+{
+ struct line* lines;
+ unsigned int linecount;
+ double z;
+};
+
+struct vectors
+{
+ struct vectorlayer* layers;
+ unsigned int layercount;
+ unsigned int current;
+};
+
+static void vector_draw(struct vectors* v, double startx, double starty, double endx, double endy, double z)
+{
+ if(v->layers && v->layers[v->current].z!=z)
+ {
+ for(v->current=0; v->current<v->layercount; ++v->current)
+ {
+ if(v->layers[v->current].z==z){break;}
+ }
+ }
+ if(v->current==v->layercount)
+ {
+ v->layers=realloc(v->layers, sizeof(struct vectorlayer)*(++v->layercount));
+ v->layers[v->current].z=z;
+ v->layers[v->current].lines=0;
+ v->layers[v->current].linecount=0;
+ }
+ struct vectorlayer* vl=&v->layers[v->current];
+ vl->lines=realloc(vl->lines, sizeof(struct line)*(++vl->linecount));
+ struct line* l=&vl->lines[vl->linecount-1];
+ l->x[0]=startx;
+ l->x[1]=endx;
+ l->y[0]=starty;
+ l->y[1]=endy;
+}
+
+int gtool_preview(const char* filename, struct img* img, double angle, double zrot)
+{
+ struct gcode* gcode=gcode_read(filename);
+ if(!gcode){return -1;}
+ const struct gcommand* cmd;
+ char tty=isatty(fileno(stdout));
+ double oldx=0;
+ double oldy=0;
+ double oldz=0;
+ double olde=0;
+ struct vectors vectors={0,0,0};
+ double max[]={NAN,NAN,NAN};
+ double min[]={NAN,NAN,NAN};
+ // Loop through gcode and analyze commands
+ while((cmd=gcode_readcommand(gcode)))
+ {
+ // Show progress if outputting to a tty
+ if(tty){printf("%.2f%%\r", (double)gcode->pos*100/(double)gcode->size);}
+ if(cmd->key=='G' && cmd->num<=1)
+ {
+ double xval=gcommand_getparam(cmd, 'X');
+ double yval=gcommand_getparam(cmd, 'Y');
+ double zval=gcommand_getparam(cmd, 'Z');
+ double eval=gcommand_getparam(cmd, 'E');
+ char move=(!isnan(xval) || !isnan(yval));
+ if(isnan(xval)){xval=oldx;}
+ if(isnan(yval)){yval=oldy;}
+ if(isnan(zval)){zval=oldz;}
+ if(!isnan(eval) && eval>olde && move)
+ {
+ olde=eval;
+ vector_draw(&vectors, oldx, oldy, xval, yval, zval);
+ }
+ oldx=xval;
+ oldy=yval;
+ oldz=zval;
+ if(isnan(max[0]) || xval>max[0]){max[0]=xval;}
+ if(isnan(max[1]) || yval>max[1]){max[1]=yval;}
+ if(isnan(max[2]) || zval>max[2]){max[2]=zval;}
+ if(isnan(min[0]) || xval<min[0]){min[0]=xval;}
+ if(isnan(min[1]) || yval<min[1]){min[1]=yval;}
+ if(isnan(min[2]) || zval<min[2]){min[2]=zval;}
+ }
+ }
+ gcode_free(gcode);
+ double center[]={(min[0]+max[0])/2, (min[1]+max[1])/2};
+ // Figure out how to scale things
+ double scale=0; // max[0];
+ unsigned int i;
+ for(i=0; i<3; ++i)
+ {
+ double dist=fabs(max[i]-min[i]);
+ if(dist>scale){scale=dist;}
+ }
+ scale=(img->width>img->height)?(img->height/scale):(img->width/scale);
+ scale*=sin(45);
+// TODO: Intermediate bitmap for shading? (plus then the final bitmap can be navigated with less re-processing. but that would be pixely)
+ while(1)
+ {
+ if(fabs(angle)>=M_PI){angle=(angle>0)?-M_PI:M_PI;}
+ // Figure out pixels-per-mm to find out how much wider lines need to be on high-res displays/methods. lines should be 0.4mm assuming "standard" nozzles
+ int linescale=ceil(scale*0.4);
+ unsigned int i;
+// To center onto a non-0 center bitmap, subtract center[], add width/2 / height/2
+#define cx(n) ((n-center[0])*scale)
+#define cy(n) ((n-center[1])*scale)
+ for(i=0; i<vectors.layercount; ++i)
+ {
+// TODO: Could probably save a lot of time by not doing these switches and instead having a start, end, and step
+if(fabs(angle)>M_PI_2){i=vectors.layercount-1-i;}
+ unsigned char c=80+i*175/vectors.layercount;
+ unsigned int j;
+ for(j=0; j<vectors.layers[i].linecount; ++j)
+ {
+ struct line* oldl=&vectors.layers[i].lines[j];
+// TODO: Do this more cleanly
+struct line line;
+struct line* l=&line;
+l->y[0]=sin(zrot)*cy(oldl->y[0])+cos(zrot)*cx(oldl->x[0]);
+l->x[0]=sin(zrot+M_PI_2)*cy(oldl->y[0])+cos(zrot+M_PI_2)*cx(oldl->x[0]);
+l->y[1]=sin(zrot)*cy(oldl->y[1])+cx(cos(zrot)*oldl->x[1]);
+l->x[1]=sin(zrot+M_PI_2)*cy(oldl->y[1])+cos(zrot+M_PI_2)*cx(oldl->x[1]);
+l->y[0]=l->y[0]*cos(angle)+vectors.layers[i].z*scale*sin(angle);
+l->y[1]=l->y[1]*cos(angle)+vectors.layers[i].z*scale*sin(angle);
+#define polcmp(n) ((n[0]>=0&&n[1]>=0) || (n[0]<0&&n[1]<0))
+ // Skip lines that are just out of view (saves resources when zoomed in far)
+ if(fabs(l->x[0])>img->width/2 && fabs(l->x[1])>img->width/2 && polcmp(l->x)){continue;}
+ if(fabs(l->y[0])>img->height/2 && fabs(l->y[1])>img->height/2 && polcmp(l->y)){continue;}
+ double stepx;
+ double stepy;
+ double steps;
+ // Figure out whether to go x/y or y/x
+ if(fabs(l->x[0]-l->x[1])>fabs(l->y[0]-l->y[1]))
+ {
+ steps=fabs(l->x[0]-l->x[1]);
+ stepx=(l->x[1]>l->x[0])?1:-1;
+ stepy=(l->y[1]-l->y[0])/steps;
+ }else{
+ steps=fabs(l->y[0]-l->y[1]);
+ stepy=(l->y[1]>l->y[0])?1:-1;
+ stepx=(l->x[1]-l->x[0])/steps;
+ }
+// Expand the line perpendicular to the line's angle
+double lineangle=atan2(stepx,stepy);
+double linestepx=sin(lineangle+M_PI_2);
+double linestepy=cos(lineangle+M_PI_2);
+int m;
+for(m=0; m<linescale; ++m)
+{
+ double k;
+ for(k=0; k<steps; ++k)
+ {
+ unsigned int x=(l->x[0]+img->width/2)+stepx*k+linestepx*(m-linescale/2);
+ unsigned int y=(l->y[0]+img->height/2)+stepy*k+linestepy*(m-linescale/2);
+ if(x>=img->width || y>=img->height){continue;}
+ img->drawpixel(img, x, y, c, c/1.05, c/2);
+ }
+ }
+}
+ if(fabs(angle)>M_PI_2){i=vectors.layercount-1-i;}
+ }
+ img->display(img);
+ img->clear(img);
+ if(img->input)
+ {
+ char quit=0;
+ switch(img->input(img))
+ {
+ case PREVIEW_XRP: angle+=M_PI/18; break;
+ case PREVIEW_XRM: angle-=M_PI/18; break;
+ case PREVIEW_ZRP: zrot+=M_PI/18; break;
+ case PREVIEW_ZRM: zrot-=M_PI/18; break;
+ case PREVIEW_ZOOMP: scale*=1.5; break;
+ case PREVIEW_ZOOMM: scale/=1.5; break;
+ case PREVIEW_QUIT: quit=1; break;
+ case PREVIEW_NOINPUT: break; // TODO: Somehow restart waiting for input. Don't bother redrawing the same perspective
+ }
+ if(quit){break;}
+ }else{break;}
+ }
+ if(img->clean){img->clean(img);}
+ return 0;
+}
+
+int gtool_preview_arg(int argc, char** argv)
+{
+ const char* filename=0;
+ const char* outfile=0;
+ double rx=0,rz=0;
+ int width=0,height=0;
+ struct img*(*imginit)(int width, int height, const char* filename)=0;
+ int i;
+ for(i=2; i<argc; ++i)
+ {
+ if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
+ {
+ filename=0; // Setting filename to 0 is a shortcut to avoid having to track the help option, both cases show the help
+ break;
+ }
+ else if(!strcmp(argv[i], "--rx") && i+1<argc)
+ {
+ rx=strtod(argv[++i],0)*M_PI/180;
+ }
+ else if(!strcmp(argv[i], "--rz") && i+1<argc)
+ {
+ rz=strtod(argv[++i],0)*M_PI/180;
+ }
+ else if((!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) && i+2<argc)
+ {
+ if(!strcmp(argv[i+1], "term")){imginit=gtool_preview_term;}
+ else if(!strcmp(argv[i+1], "pnm")){imginit=gtool_preview_pnm;}
+ i+=2;
+ outfile=argv[i];
+ }
+ else if((!strcmp(argv[i], "-s") || !strcmp(argv[i], "--size")) && i+1<argc)
+ {
+ char* end=0;
+ width=strtol(argv[++i], &end, 0);
+ if(end && end[0]){height=strtol(&end[1], 0, 0);}else{height=width;}
+ }
+ else if(!filename)
+ {
+ filename=argv[i];
+ }
+ }
+ if(!filename)
+ {
+ printf("Usage: %s %s [options] <filename>\n"
+ "Options:\n"
+ "-h/--help = display this help text\n"
+ "--r[xz] <angle> = rotate X or Z <angle> degrees\n"
+ "-o/--output <method> <file> = select output method and filename\n"
+ "-s/--size <width>x<height> = set output size\n"
+ "TODO: scale etc.\n", argv[0], argv[1]);
+ return -1;
+ }
+// TODO: Mechanism for selecting method by what's available. Not relevant until there's another direct display method
+ if(!imginit){imginit=gtool_preview_term;}
+ struct img* img=imginit(width,height,outfile);
+ return gtool_preview(filename, img, rx, rz);
+}
diff --git a/preview.h b/preview.h
new file mode 100644
index 0000000..4e664eb
--- /dev/null
+++ b/preview.h
@@ -0,0 +1,40 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2019 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+enum previewcontrol
+{
+ PREVIEW_XRP=0,
+ PREVIEW_XRM,
+ PREVIEW_ZRP,
+ PREVIEW_ZRM,
+// TODO: Control Y as well? And translation?
+ PREVIEW_ZOOMP,
+ PREVIEW_ZOOMM,
+ PREVIEW_QUIT,
+ PREVIEW_NOINPUT
+};
+
+struct img
+{
+ int width;
+ int height;
+ void(*drawpixel)(struct img*,int x, int y, unsigned char r, unsigned char g, unsigned char b);
+ void(*clear)(struct img*);
+ void(*display)(struct img*);
+ enum previewcontrol(*input)(struct img*);
+ void(*clean)(struct img*);
+};
diff --git a/preview_pnm.c b/preview_pnm.c
new file mode 100644
index 0000000..0893ffa
--- /dev/null
+++ b/preview_pnm.c
@@ -0,0 +1,98 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2019 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "preview.h"
+
+struct img_pnm
+{
+ struct img img;
+ unsigned char* buf;
+ FILE* out;
+ char bin;
+};
+
+static void cb_drawpixel(struct img* imgp,int x, int y, unsigned char r, unsigned char g, unsigned char b)
+{
+ struct img_pnm* img=(struct img_pnm*)imgp;
+ img->buf[(x+y*imgp->width)*3]=r;
+ img->buf[(x+y*imgp->width)*3+1]=g;
+ img->buf[(x+y*imgp->width)*3+2]=b;
+}
+
+static void cb_clear(struct img* imgp)
+{
+ struct img_pnm* img=(struct img_pnm*)imgp;
+ memset(img->buf, 0, img->img.width*img->img.height*3);
+}
+
+static void cb_display(struct img* imgp)
+{
+ struct img_pnm* img=(struct img_pnm*)imgp;
+ unsigned int i;
+ fprintf(img->out, "P%i\n# Generated by gcodetool <https://gcodetool.ion.nu/>\n%i %i\n255\n", 3+img->bin*3, imgp->width, imgp->height);
+ if(img->bin)
+ {
+ fwrite(img->buf, 3, imgp->width*imgp->height, img->out);
+ }else{
+ for(i=0; i<imgp->width*imgp->height*3; ++i)
+ {
+ fprintf(img->out, "%hhu ", img->buf[i]);
+// TODO: Would X/Y arithmetics be faster than this one conditional?
+// if(!((i+1)%imgp->width)){write(img->fd, "\n", 1); i+=imgp->width;}
+ }
+ }
+}
+
+static void cb_clean(struct img* imgp)
+{
+ struct img_pnm* img=(struct img_pnm*)imgp;
+ free(img->buf);
+ free(img);
+}
+
+struct img* gtool_preview_pnm(int width, int height, const char* filename)
+{
+ struct img_pnm* img=malloc(sizeof(struct img_pnm));
+ if(filename && strcmp(filename, "-"))
+ {
+ img->out=fopen(filename, "w");
+ img->bin=1;
+ }else{
+ img->out=stdout;
+ img->bin=0;
+ }
+ if(!width || !height) // Make up sizes if not specified
+ {
+ if(!width){width=1024;}
+ if(!height){height=1024;}
+ }
+ // Allocate buffer
+ img->img.width=width;
+ img->img.height=height;
+ img->buf=malloc(width*height*3);
+ memset(img->buf, 0, width*height);
+ // Set callbacks
+ img->img.drawpixel=cb_drawpixel;
+ img->img.clear=cb_clear;
+ img->img.display=cb_display;
+ img->img.input=0;
+ img->img.clean=cb_clean;
+ return (struct img*)img;
+}
diff --git a/preview_term.c b/preview_term.c
new file mode 100644
index 0000000..76e8c8b
--- /dev/null
+++ b/preview_term.c
@@ -0,0 +1,144 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2019 alicia@ion.nu
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include "preview.h"
+
+struct img_term
+{
+ struct img img;
+ unsigned char* buf;
+ struct termios term_orig;
+ int fd;
+};
+
+static void termcolor(int fd, unsigned char r1, unsigned char g1, unsigned char b1, unsigned char r2, unsigned char g2, unsigned char b2)
+{
+// 24bit, TODO: make this togglable somehow
+/*
+ printf("\e[38;2;%hhu;%hhu;%hhum"
+ "\e[48;2;%hhu;%hhu;%hhum"
+ "\u2580" // 9600
+ "\e[0m", r1, g1, b1, r2, g2, b2);
+*/
+// 8bit
+ unsigned int top=232+r1/11;
+ unsigned int bottom=232+r2/11;
+ dprintf(fd,
+ "\e[38;5;%um"
+ "\e[48;5;%um"
+ "\u2580" // 9600
+ "\e[0m", top, bottom);
+}
+
+static void cb_drawpixel(struct img* imgp,int x, int y, unsigned char r, unsigned char g, unsigned char b)
+{
+ struct img_term* img=(struct img_term*)imgp;
+ img->buf[x+y*img->img.width]=r/3+g/3+b/3; // TODO: handle >8 bits
+}
+
+static void cb_clear(struct img* imgp)
+{
+ struct img_term* img=(struct img_term*)imgp;
+ memset(img->buf, 0, img->img.width*img->img.height); // TODO: handle >8 bits
+}
+
+static void cb_display(struct img* imgp)
+{
+ struct img_term* img=(struct img_term*)imgp;
+ write(img->fd, "\e[H", 3);
+ unsigned char* buf=img->buf;
+ unsigned int i;
+ for(i=0; i<imgp->width*(imgp->height-1); ++i)
+ {
+ termcolor(img->fd, buf[i], buf[i], buf[i], buf[i+imgp->width], buf[i+imgp->width], buf[i+imgp->width]);
+// TODO: Would X/Y arithmetics be faster than this one conditional?
+ if(!((i+1)%imgp->width) && i+1<imgp->width*(imgp->height-1)){write(img->fd, "\n", 1); i+=imgp->width;}
+ }
+}
+
+static enum previewcontrol cb_input(struct img* imgp)
+{
+ char key[10];
+ int keylen;
+ if((keylen=read(0, key, 9))>0)
+ {
+ if(keylen==3 && key[0]=='\e' && key[1]=='[')
+ {
+ return key[2]-'A';
+ }
+ if(keylen==1)
+ {
+ if(key[0]=='q'){return PREVIEW_QUIT;}
+ if(key[0]=='+'){return PREVIEW_ZOOMP;}
+ if(key[0]=='-'){return PREVIEW_ZOOMM;}
+ }
+ }
+ return PREVIEW_NOINPUT;
+}
+
+static void cb_clean(struct img* imgp)
+{
+ struct img_term* img=(struct img_term*)imgp;
+ tcsetattr(0,TCSANOW,&img->term_orig);
+ free(img->buf);
+ free(img);
+}
+
+struct img* gtool_preview_term(int width, int height, const char* filename)
+{
+ struct img_term* img=malloc(sizeof(struct img_term));
+ if(filename && strcmp(filename, "-"))
+ {
+ img->fd=open(filename, O_WRONLY|O_TRUNC);
+ }else{
+ img->fd=1;
+ }
+ if(!width || !height) // Get screen size
+ {
+ struct winsize ws;
+ ioctl(1, TIOCGWINSZ, &ws);
+// TODO: Handle failure to get terminal size, will happen for non '-' filenames
+ if(!width){width=ws.ws_col;}
+ if(!height){height=ws.ws_row*2;} // 2 pixels per row thanks to half solid symbol
+// printf("Screen size: %ix%i\n", ws.ws_row, ws.ws_col);
+ }
+ // Allocate buffer
+ img->img.width=width;
+ img->img.height=height;
+ img->buf=malloc(width*height); // TODO: Handle >8 bit as well
+ memset(img->buf, 0, width*height);
+ // Set terminal mode to non-canonical (not line-buffered) and without echo
+ struct termios term;
+ tcgetattr(0,&term);
+ tcgetattr(0,&img->term_orig);
+ term.c_lflag^=ICANON|ECHO;
+ tcsetattr(0,TCSANOW,&term);
+ // Set callbacks
+ img->img.drawpixel=cb_drawpixel;
+ img->img.clear=cb_clear;
+ img->img.display=cb_display;
+ img->img.input=cb_input;
+ img->img.clean=cb_clean;
+ return (struct img*)img;
+}