$ git clone http://gcodetool.ion.nu/gcodetool.git
commit bcf7333a0b0f67a0d5d20d17d3f87d27c370c4db
Author: Alicia <...>
Date: Tue Jun 11 17:54:25 2019 +0000
Initial commit.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a85adc3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+CFLAGS=-Wall -g3
+gcodetool: main.o gcode.o mirror.o move.o info.o
+ $(CC) $^ -lm -o $@
+static: CC+=-static
+static: gcodetool
diff --git a/gcode.c b/gcode.c
new file mode 100644
index 0000000..bf2d6d9
--- /dev/null
+++ b/gcode.c
@@ -0,0 +1,187 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2018-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 <ctype.h>
+#include <math.h>
+#include "gcode.h"
+#define BLOCK 1024
+
+int getprecision(double v) // For printing doubles without excess zeros, use with format %.*f and macro pd(double)
+{
+ char buf[snprintf(0,0,"%f",v)+1];
+ sprintf(buf,"%f",v);
+ char* dot=strchr(buf,'.');
+ if(!dot){dot=strchr(buf,',');}
+ if(!dot){return 0;}
+ int len=strlen(dot)-1;
+ while(dot[len]=='0'){--len;}
+// printf("Precision of %f: %i\n", v, len);
+ return len;
+}
+
+struct gcode* gcode_read(const char* filename)
+{
+ FILE* in;
+ if(filename && strcmp(filename, "-"))
+ {
+ in=fopen(filename, "r");
+// printf("fopen(%s): %p\n", filename, in);
+ }else{in=stdin;}
+ if(!in){return 0;}
+ struct gcode* gcode=malloc(sizeof(struct gcode));
+ gcode->code=0;
+ gcode->pos=0;
+ gcode->size=0;
+ while(!feof(in))
+ {
+ gcode->size+=1024;
+ gcode->code=realloc(gcode->code, gcode->size);
+ int r;
+ if((r=fread(&gcode->code[gcode->size-1024], 1, 1024, in))<1024)
+ {
+ if(r<1 && !feof(in)){perror("read"); return 0;}
+ gcode->size-=1024-r;
+ }
+ }
+ return gcode;
+}
+
+int gcode_write(struct gcode* gcode, const char* filename);
+
+double gcode_readnumber(struct gcode* gcode)
+{
+ char buf[1024];
+ unsigned int pos=0;
+ while(gcode->pos<gcode->size && pos<1023 && memchr("0123456789.-\r\n\t ", gcode->code[gcode->pos], 16))
+ {
+ if(memchr("\r\n\t ", gcode->code[gcode->pos], 4)){++gcode->pos; continue;}
+ buf[pos++]=gcode->code[gcode->pos++];
+ }
+ buf[pos]=0;
+ return strtod(buf, 0);
+}
+
+int gcode_seekcmd(struct gcode* gcode, char cmd)
+{
+ if(gcode->pos>=gcode->size){return 0;}
+// TODO: skip text commands
+ cmd=toupper(cmd);
+ char comment=0;
+ while(toupper(gcode->code[gcode->pos])!=cmd || comment)
+ {
+ if(gcode->code[gcode->pos]==';'){comment=1;}
+ ++gcode->pos;
+ if(gcode->pos>=gcode->size){return 0;}
+ if(gcode->code[gcode->pos]=='\n'){comment=0;}
+ }
+ return 1;
+}
+
+double gcode_getparam(struct gcode* gcode, char param)
+{
+// TODO: skip text commands
+ param=toupper(param);
+ unsigned int pos=gcode->pos;
+ char comment=0;
+ while(toupper(gcode->code[gcode->pos])!=param || comment)
+ {
+ if(gcode->code[gcode->pos]==';'){comment=1;}
+ ++gcode->pos;
+ if(gcode->pos>=gcode->size){gcode->pos=pos; return nan("");}
+ if(strchr(GCODE_KEYS, toupper(gcode->code[gcode->pos]))){gcode->pos=pos; return nan("");} // Hit next command
+ if(gcode->code[gcode->pos]=='\n'){comment=0;}
+ }
+ ++gcode->pos;
+ double num=gcode_readnumber(gcode);
+ gcode->pos=pos;
+ return num;
+}
+
+void gcode_nextline(struct gcode* gcode)
+{
+ while(gcode->pos<gcode->size && gcode->code[gcode->pos]!='\n'){++gcode->pos;}
+ ++gcode->pos;
+}
+
+const struct gcommand* gcode_readcommand(struct gcode* gcode)
+{
+ // Find command
+ while(gcode->pos<gcode->size && !strchr(GCODE_KEYS, toupper(gcode->code[gcode->pos])))
+ {
+ if(gcode->code[gcode->pos]==';'){gcode_nextline(gcode); continue;} // Skip comments
+ ++gcode->pos;
+ }
+ if(gcode->pos>=gcode->size){return 0;}
+ static struct gcommand cmd={0,0,0,0,{0}};
+ switch(cmd.cmdtype) // I suppose functionally this doesn't make a difference, but it might if more types come up
+ {
+ case GCODE_CMD_PARAM: free(cmd.params); break;
+ case GCODE_CMD_STR: free(cmd.string); break;
+ }
+ cmd.key=toupper(gcode->code[gcode->pos]);
+ ++gcode->pos;
+ cmd.num=gcode_readnumber(gcode);
+ if(cmd.key=='M' && (cmd.num==117 || cmd.num==118))
+ {
+ cmd.cmdtype=GCODE_CMD_STR;
+ unsigned int start=gcode->pos;
+ gcode_nextline(gcode);
+ cmd.paramcount=gcode->pos-start-1;
+ cmd.string=strndup(&gcode->code[start], cmd.paramcount);
+ return &cmd;
+ }
+ cmd.cmdtype=GCODE_CMD_PARAM;
+ cmd.paramcount=0;
+ cmd.params=0;
+ int paramsize=0;
+ // readnumber should dump us right at the next command or parameter
+ while(gcode->pos<gcode->size && !strchr(GCODE_KEYS, toupper(gcode->code[gcode->pos])))
+ {
+ if(gcode->code[gcode->pos]==';'){gcode_nextline(gcode); continue;} // Skip comments
+ ++cmd.paramcount;
+ if(cmd.paramcount>paramsize)
+ {
+ cmd.params=realloc(cmd.params, sizeof(struct gparam)*(paramsize+=10));
+ }
+ cmd.params[cmd.paramcount-1].key=toupper(gcode->code[gcode->pos]);
+ ++gcode->pos;
+ cmd.params[cmd.paramcount-1].num=gcode_readnumber(gcode);
+ }
+ return &cmd;
+}
+
+double gcommand_getparam(const struct gcommand* cmd, char key)
+{
+ if(cmd->cmdtype!=GCODE_CMD_PARAM){return nan("");}
+ unsigned int i;
+ for(i=0; i<cmd->paramcount; ++i)
+ {
+ if(cmd->params[i].key==key){return cmd->params[i].num;}
+ }
+ return nan("");
+}
+
+int gcode_fwrite(struct gcode* gcode, FILE* out);
+int gcode_writeuntil(struct gcode* gcode, char cmd, FILE* out);
+void gcode_free(struct gcode* gcode)
+{
+ free(gcode->code);
+ free(gcode);
+}
diff --git a/gcode.h b/gcode.h
new file mode 100644
index 0000000..65f6560
--- /dev/null
+++ b/gcode.h
@@ -0,0 +1,61 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2018-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/>.
+*/
+#define GCODE_KEYS "GMNT"
+struct gcode
+{
+ char* code;
+ unsigned int size;
+ unsigned int pos;
+};
+struct gparam
+{
+ char key;
+ double num;
+};
+enum gcode_cmdtype
+{
+ GCODE_CMD_PARAM=0,
+ GCODE_CMD_STR
+};
+struct gcommand
+{
+ char key;
+ int num;
+ enum gcode_cmdtype cmdtype;
+ unsigned int paramcount;
+ union
+ {
+ struct gparam* params;
+ char* string;
+ };
+};
+
+#define pd(d) getprecision(d), d
+extern int getprecision(double v);
+
+#define gcode_reset(gc) (gc)->pos=0
+extern struct gcode* gcode_read(const char* filename);
+extern int gcode_write(struct gcode* gcode, const char* filename);
+extern double gcode_readnumber(struct gcode* gcode);
+extern int gcode_seekcmd(struct gcode* gcode, char cmd);
+extern double gcode_getparam(struct gcode* gcode, char param);
+extern const struct gcommand* gcode_readcommand(struct gcode* gcode);
+extern double gcommand_getparam(const struct gcommand* cmd, char key);
+extern int gcode_fwrite(struct gcode* gcode, FILE* out);
+extern int gcode_writeuntil(struct gcode* gcode, char cmd, FILE* out);
+extern void gcode_free(struct gcode* gcode);
diff --git a/info.c b/info.c
new file mode 100644
index 0000000..36bca32
--- /dev/null
+++ b/info.c
@@ -0,0 +1,195 @@
+/*
+ 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"
+
+struct minmax
+{
+ double min;
+ double max;
+ double last;
+ char rel;
+};
+void minmax(struct minmax* mm, double now)
+{
+// printf("minmax({min: %f, max: %f, last: %f, rel: %s}, now: %f)\n", mm->min, mm->max, mm->last, mm->rel?"True":"False", now);
+ if(mm->rel)
+ {
+ if(mm->last==now){return;} // Unchanged
+ now-=mm->last;
+ mm->last+=now;
+ }else{
+ mm->last=now;
+ }
+ if(isnan(mm->min) || now<mm->min){mm->min=now;}
+ if(isnan(mm->max) || now>mm->max){mm->max=now;}
+}
+
+int gtool_info(const char* filename)
+{
+ struct gcode* gcode=gcode_read(filename);
+ if(!gcode){return -1;}
+ gcode_reset(gcode);
+ struct minmax layer={nan(""),nan(""),0,1};
+ struct minmax x={nan(""),nan(""),0,0};
+ struct minmax y={nan(""),nan(""),0,0};
+ struct minmax z={0,nan(""),0,0};
+ struct minmax e={nan(""),nan(""),0,0};
+ struct minmax emove={nan(""),nan(""),0,0}; // Printing moves
+ struct minmax retraction={nan(""),nan(""),0,1};
+ const struct gcommand* cmd;
+ double zpos=nan("");
+ char tty=isatty(fileno(stdout));
+ double* tooltemps=0;
+ unsigned int tooltempcount=0;
+ double* bedtemps=0;
+ unsigned int bedtempcount=0;
+ // 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(zval)){zpos=zval;} // Take things like 'lift head' into account, don't treat as Z until there's a positive E
+ // Printing line length
+ if(!isnan(eval) && move && eval>0)
+ {
+ double xdist=x.last-(isnan(xval)?x.last:xval);
+ double ydist=y.last-(isnan(yval)?y.last:yval);
+ double dist=sqrt(xdist*xdist+ydist*ydist);
+// printf("Print distance: %f\n", dist);
+ minmax(&emove, dist);
+ // Layer heights
+ if(!isnan(zpos))
+ {
+ minmax(&layer, zpos);
+ zpos=nan("");
+ }
+ }
+ // Basic XYZ
+ if(!isnan(xval)){minmax(&x, xval);}
+ if(!isnan(yval)){minmax(&y, yval);}
+ if(!isnan(zval)){minmax(&z, zval);}
+ if(!move && isnan(zval) && !isnan(eval) && (retraction.min<0 || eval<e.last)) // First retraction must be E-n
+ {
+ static double eval_=nan(""); // Buffer to ignore the last E motion, which often isn't indicative of general retractions
+ if(!isnan(eval_))
+ {
+ // TODO: count number of retractions?
+ minmax(&retraction, eval_);
+ }
+ eval_=eval;
+ if(eval<e.last){retraction.last=e.last;} // Count retraction from the last extrusion position
+ }
+ if(!isnan(eval)){minmax(&e, eval);}
+ }
+ else if(cmd->key=='G' && cmd->num==92) // Set current value
+ {
+ double xval=gcommand_getparam(cmd, 'X');
+ double yval=gcommand_getparam(cmd, 'Y');
+ double zval=gcommand_getparam(cmd, 'Z');
+ double eval=gcommand_getparam(cmd, 'E');
+ if(!isnan(xval)){x.last=xval;}
+ if(!isnan(yval)){y.last=yval;}
+ if(!isnan(zval)){z.last=zval;}
+ if(!isnan(eval)){e.last=eval;}
+ }
+ else if(cmd->key=='M' && (cmd->num==140 || cmd->num==190 || cmd->num==104 || cmd->num==109))
+ {
+ double val=gcommand_getparam(cmd, 'S');
+ if(cmd->num>110) // Bed is 140 and 190, tool is 104 and 109 ('set' and 'wait for'/'set+wait for')
+ {
+ if(bedtempcount>0 && bedtemps[bedtempcount-1]==val){continue;}
+ bedtemps=realloc(bedtemps, sizeof(double)*(++bedtempcount));
+ bedtemps[bedtempcount-1]=val;
+ }else{
+ if(tooltempcount>0 && tooltemps[tooltempcount-1]==val){continue;}
+ tooltemps=realloc(tooltemps, sizeof(double)*(++tooltempcount));
+ tooltemps[tooltempcount-1]=val;
+ }
+ }
+ }
+ gcode_free(gcode);
+ // Write report
+ unsigned int i;
+ printf("Maximum print move distance: %.*f\n", pd(emove.max));
+ printf("Minimum print move distance: %.*f\n", pd(emove.min));
+ printf("Bed temperature(s): ");
+ for(i=0; i<bedtempcount; ++i)
+ {
+ if(i==bedtempcount-1 && bedtemps[i]==0){break;}
+ printf("%s%.*f°C", (i>0)?", ":"", pd(bedtemps[i]));
+ }
+ if(i>0 && i==bedtempcount){printf(" (WARNING: This gcode file appears to leave to bed heater on)");}
+ printf("\n");
+ printf("Tool temperature(s): ");
+ for(i=0; i<tooltempcount; ++i)
+ {
+ if(i==tooltempcount-1 && tooltemps[i]==0){break;}
+ printf("%s%.*f°C", (i>0)?", ":"", pd(tooltemps[i]));
+ }
+ if(i>0 && i==tooltempcount){printf(" (WARNING: This gcode file appears to leave to tool heater on)");}
+ printf("\n");
+ printf("Size(X): %.*fmm (%.*f - %.*f)\n", pd(x.max-x.min), pd(x.min), pd(x.max));
+ printf("Size(Y): %.*fmm (%.*f - %.*f)\n", pd(y.max-y.min), pd(y.min), pd(y.max));
+ printf("Size(Z): %.*fmm (%.*f - %.*f)\n", pd(z.max-z.min), pd(z.min), pd(z.max));
+ printf("Maximum layer height: %.*fmm\n", pd(layer.max));
+ printf("Minimum layer height: %.*fmm\n", pd(layer.min));
+ printf("Retraction length: %.*fmm\n", pd(-retraction.min));
+ printf("Un-retraction length: %.*fmm\n", pd(retraction.max));
+ printf("Filament use: %.*fmm\n", pd(e.max-e.min));
+ free(bedtemps);
+ free(tooltemps);
+ return 0;
+}
+
+int gtool_info_arg(int argc, char** argv)
+{
+ 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(!filename)
+ {
+ filename=argv[i];
+ }
+ }
+ if(!filename)
+ {
+ printf("Usage: %s %s [options] <filename>\n"
+ "Options:\n"
+ "-h/--help = display this help text\n", argv[0], argv[1]);
+ return -1;
+ }
+ return gtool_info(filename);
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..91b5d6d
--- /dev/null
+++ b/main.c
@@ -0,0 +1,52 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2018-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>
+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);
+
+int main(int argc, char** argv)
+{
+ if(argc>2 && !strncmp(argv[1], "mirror", 6) && memchr("xyzXYZ", argv[1][6], 6) && !argv[1][7])
+ {
+ return gtool_mirror_arg(argc, argv);
+ }
+ else if(argc>2 && !strncmp(argv[1], "move", 4) && memchr("xyzXYZ", argv[1][4], 6) && !argv[1][5])
+ {
+ return gtool_move_arg(argc, argv);
+ }
+ else if(argc>2 && !strcmp(argv[1], "info"))
+ {
+ return gtool_info_arg(argc, argv);
+ }else{
+ printf("Usage: %s <subcommand> [options] <file>\n"
+ "Supported subcommands include:\n"
+ "mirror[XYZ]\n"
+ "move[XYZ]\n"
+ "info\n"
+"\nTODO:\n"
+"rotate[XYZ]\n"
+"speed (change speeds for selectable cases like travel, extrude)\n"
+"remove (exclude parts of the object)\n"
+"preview, somehow\n"
+"scale (will also need to scale E)\n", argv[0]);
+ return 1;
+ }
+ return 0;
+}
diff --git a/mirror.c b/mirror.c
new file mode 100644
index 0000000..2a0cfb5
--- /dev/null
+++ b/mirror.c
@@ -0,0 +1,100 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2018 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 <ctype.h>
+#include "gcode.h"
+
+int gtool_mirror(const char* filename, const char* outfilename, char axis)
+{
+ struct gcode* gcode=gcode_read(filename);
+ if(!gcode){return -1;}
+ printf("Loaded %u bytes of gcode\n", gcode->size);
+ double min=999999999;
+ double max=-999999999;
+ while(gcode_seekcmd(gcode, axis))
+ {
+ ++gcode->pos;
+ double v=gcode_readnumber(gcode);
+ if(v<min){min=v;}
+ if(v>max){max=v;}
+ }
+// printf("%c min: %g\n", axis, min);
+// printf("%c max: %g\n", axis, max);
+ double center=(min+max)/2;
+ printf("%c center/pivot point: %g\n", axis, center);
+ gcode_reset(gcode);
+ // Loop through gcode, cloning most, processing axis coordinates before writing mirrored values
+ FILE* out=fopen(outfilename, "w");
+ unsigned int lastwrite=0;
+ while(gcode_seekcmd(gcode, axis))
+ {
+ ++gcode->pos;
+ fwrite(&gcode->code[lastwrite], 1, gcode->pos-lastwrite, out);
+ double v=gcode_readnumber(gcode);
+ lastwrite=gcode->pos;
+ fprintf(out, "%g ", center*2-v);
+ }
+ // Write the remaining gcode
+ fwrite(&gcode->code[lastwrite], 1, gcode->pos-lastwrite, out);
+ fclose(out);
+ gcode_free(gcode);
+ return 0;
+}
+
+int gtool_mirror_arg(int argc, char** argv)
+{
+ const char* filename=0;
+ const char* outfilename=0;
+ char axis=toupper(argv[1][6]);
+ 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], "-o") && i+1<argc)
+ {
+ outfilename=argv[++i];
+ }
+ else if(!filename)
+ {
+ filename=argv[i];
+ }else{
+ filename=0;
+ break;
+ }
+ }
+ if(!filename)
+ {
+ printf("Usage: %s %s [options] <filename>\n"
+ "Options:\n"
+ "-o <filename> = leave the input file unchanged and write\n"
+ " the changed version to <filename>\n"
+ "-h/--help = display this help text\n", argv[0], argv[1]);
+ return -1;
+ }
+ if(!outfilename)
+ {
+ outfilename=filename;
+ }
+ if(axis=='Z'){printf("Warning! you have chosen to mirror on the Z axis, which is unlikely to give the desired outcome (for now anyway)\n");}
+ return gtool_mirror(filename, outfilename, axis);
+}
diff --git a/move.c b/move.c
new file mode 100644
index 0000000..9759c75
--- /dev/null
+++ b/move.c
@@ -0,0 +1,93 @@
+/*
+ gcodetool, a utility for analyzing/modifying already sliced parts
+ Copyright (C) 2018 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 <ctype.h>
+#include "gcode.h"
+
+int gtool_move(const char* filename, const char* outfilename, char axis, double distance)
+{
+ struct gcode* gcode=gcode_read(filename);
+ if(!gcode){return -1;}
+ printf("Loaded %u bytes of gcode\n", gcode->size);
+ gcode_reset(gcode);
+ // Loop through gcode, cloning most, processing axis coordinates before writing offset values
+ FILE* out=fopen(outfilename, "w");
+ unsigned int lastwrite=0;
+ while(gcode_seekcmd(gcode, axis))
+ {
+ ++gcode->pos;
+ fwrite(&gcode->code[lastwrite], 1, gcode->pos-lastwrite, out);
+ double v=gcode_readnumber(gcode);
+ lastwrite=gcode->pos;
+ fprintf(out, "%g ", v+distance);
+ }
+ // Write the remaining gcode
+ fwrite(&gcode->code[lastwrite], 1, gcode->pos-lastwrite, out);
+ fclose(out);
+ gcode_free(gcode);
+ return 0;
+}
+
+int gtool_move_arg(int argc, char** argv)
+{
+ const char* filename=0;
+ const char* outfilename=0;
+ const char* distancestr=0;
+ char axis=toupper(argv[1][4]);
+ 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], "-o") && i+1<argc)
+ {
+ outfilename=argv[++i];
+ }
+ else if(!filename)
+ {
+ filename=argv[i];
+ }
+ else if(!distancestr)
+ {
+ distancestr=argv[i];
+ }else{
+ filename=0;
+ break;
+ }
+ }
+ if(!filename || !distancestr)
+ {
+ printf("Usage: %s %s [options] <filename> <distance (mm)>\n"
+ "Options:\n"
+ "-o <filename> = leave the input file unchanged and write\n"
+ " the changed version to <filename>\n"
+ "-h/--help = display this help text\n", argv[0], argv[1]);
+ return -1;
+ }
+ if(!outfilename)
+ {
+ outfilename=filename;
+ }
+ double distance=strtod(distancestr, 0);
+ return gtool_move(filename, outfilename, axis, distance);
+}