aboutsummaryrefslogtreecommitdiffstats
path: root/src/stlfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/stlfile.c')
-rw-r--r--src/stlfile.c415
1 files changed, 415 insertions, 0 deletions
diff --git a/src/stlfile.c b/src/stlfile.c
new file mode 100644
index 0000000..88fc430
--- /dev/null
+++ b/src/stlfile.c
@@ -0,0 +1,415 @@
+#include "stlfile.h"
+
+static const char wsset[] = " \r\n\t";
+static const struct {
+ int code;
+ const char *str;
+} kwmap[] = {
+ { KW_SOLID_BEGIN, "solid" },
+ { KW_SOLID_END, "endsolid" },
+ { KW_LOOP_BEGIN, "outer loop" },
+ { KW_LOOP_END, "endloop" },
+ { KW_FACET_BEGIN, "facet normal" },
+ { KW_FACET_END, "endfacet" },
+ { KW_VERTEX, "vertex" },
+};
+
+void
+stack_init(struct stack *stack)
+{
+ stack->cap = 10;
+ stack->data = checkp(malloc(sizeof(int) * stack->cap));
+ stack->count = 0;
+}
+
+void
+stack_push(struct stack *stack, int v)
+{
+ if (stack->count == stack->cap) {
+ stack->cap *= 2;
+ stack->data = checkp(realloc(stack->data, sizeof(int) * stack->cap));
+ }
+
+ stack->data[stack->count] = v;
+ stack->count++;
+}
+
+int
+stack_pop(struct stack *stack)
+{
+ if (stack->count == 0)
+ return -1;
+
+ stack->count--;
+ return stack->data[stack->count];
+}
+
+void
+stack_free(struct stack *stack)
+{
+ free(stack->data);
+}
+
+int
+stack_ind(struct stack *stack, int search)
+{
+ int i;
+ for (i = 0; i < stack->count; i++)
+ if (stack->data[i] == search)
+ return i;
+ return -1;
+}
+
+int
+isws(char c)
+{
+ return c && strchr(wsset, c);
+}
+
+char*
+skipws(char *p)
+{
+ for (; isws(*p); p++);
+ return p;
+}
+
+char*
+consume_arg(char **start, char **end)
+{
+ char *c, *tmp;
+
+ *start = skipws(*start);
+ if (!*start) return NULL;
+ for (c = *start; *c && !isws(*c); c++);
+ tmp = *start;
+ *start = c + 1;
+ *end = c;
+ return tmp;
+}
+
+int
+consume_keyword(char **start)
+{
+ char *bp;
+ int i, len;
+
+ bp = skipws(*start);
+
+ for (i = 0; i < ARRSIZE(kwmap); i++) {
+ len = strlen(kwmap[i].str);
+ if (!strncmp(kwmap[i].str, bp, len) && (!bp[len] || isws(bp[len]))) {
+ // printf("GOT: %s\n", kwmap[i].str);
+ *start = bp + len + (bp[len] ? 1 : 0);
+ return kwmap[i].code;
+ }
+ }
+
+ return KW_UNKNOWN;
+}
+
+#define PARSE_FAIL(...) \
+ do { fprintf(stderr, "FORMAT ERR: " __VA_ARGS__); goto fail; } while (0)
+
+int
+parse_file_ascii(struct parseinfo *info, char *buf, size_t len)
+{
+ char *bp, *arg, *prev, *tmp, *end;
+ struct stack states;
+ float farg;
+ int i, kw;
+
+ stack_init(&states);
+
+ info->type = TYPE_ASCII;
+ info->loopcount = 0;
+
+ memset(info->header, 0, 80);
+
+ for (i = 0; i < 3; i++) {
+ info->bbmin[i] = INFINITY;
+ info->bbmax[i] = -INFINITY;
+ }
+
+ bp = prev = buf;
+ while ((kw = consume_keyword(&bp))) {
+ switch (kw) {
+ case KW_SOLID_BEGIN:
+ stack_push(&states, STATE_SOLID);
+ if (stack_ind(&states, KW_SOLID_BEGIN) != -1)
+ PARSE_FAIL("Multiple nested solids!\n");
+ tmp = bp;
+ if (!consume_keyword(&bp) && (arg = consume_arg(&bp, &end))) {
+ info->solidname = strndup(arg, end - arg);
+ } else {
+ bp = tmp;
+ }
+ break;
+ case KW_SOLID_END:
+ if ((kw = stack_pop(&states)) != STATE_SOLID)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ tmp = bp;
+ if (info->solidname && !consume_keyword(&bp)
+ && (arg = consume_arg(&bp, &end))) {
+ if (strncmp(info->solidname, arg, end - arg))
+ PARSE_FAIL("Solid end/begin names do not match!\n");
+ } else {
+ bp = tmp;
+ }
+ break;
+ case KW_LOOP_BEGIN:
+ stack_push(&states, STATE_LOOP);
+ if (stack_ind(&states, KW_LOOP_BEGIN) != -1)
+ PARSE_FAIL("Multiple nested loops!\n");
+ break;
+ case KW_LOOP_END:
+ if ((kw = stack_pop(&states)) != STATE_LOOP)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ info->loopcount++;
+ break;
+ case KW_FACET_BEGIN:
+ stack_push(&states, STATE_FACET);
+ if (stack_ind(&states, KW_LOOP_BEGIN) != -1)
+ PARSE_FAIL("Multiple nested facets!\n");
+ for (i = 0; i < 3; i++) {
+ if (!(arg = consume_arg(&bp, &end)))
+ PARSE_FAIL("Facet with less than 3 args!\n");
+ farg = strtof(arg, &tmp);
+ if (!isws(*tmp))
+ PARSE_FAIL("Facet with invalid arg '%s'!\n", arg);
+ }
+ break;
+ case KW_FACET_END:
+ if ((kw = stack_pop(&states)) != STATE_FACET)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ break;
+ case KW_VERTEX:
+ for (i = 0; i < 3; i++) {
+ if (!(arg = consume_arg(&bp, &end)))
+ PARSE_FAIL("Vertex with less than 3 args!\n");
+ farg = strtof(arg, &tmp);
+ if (!isws(*tmp))
+ PARSE_FAIL("Vertex with invalid arg '%s'!\n", arg);
+ info->bbmin[i] = MIN(info->bbmin[i], farg);
+ info->bbmax[i] = MAX(info->bbmax[i], farg);
+ }
+ break;
+ case KW_UNKNOWN:
+ prev = skipws(prev);
+ PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, prev);
+ }
+ prev = bp;
+ }
+
+ if (states.count)
+ PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp);
+
+ stack_free(&states);
+ return OK;
+
+fail:
+ stack_free(&states);
+ return FAIL;
+}
+
+int
+parse_file_bin(struct parseinfo *info, char *buf, size_t len)
+{
+ char *bp, *end = buf + len;
+ int i, k, m;
+ float v;
+
+ info->type = TYPE_BIN;
+
+ if (len < 84)
+ PARSE_FAIL("Truncated data! (header missing)\n");
+
+ memcpy(info->header, buf, 80);
+
+ if (strlen(buf + 1))
+ info->solidname = checkp(strdup(buf + 1));
+
+ bp = buf + 80;
+ info->loopcount = le32toh(*(uint32_t*)bp);
+
+ if (!info->loopcount) {
+ memset(info->bbmax, 0, sizeof(float) * 3);
+ memset(info->bbmin, 0, sizeof(float) * 3);
+ return OK;
+ }
+
+ for (i = 0; i < 3; i++) {
+ info->bbmin[i] = INFINITY;
+ info->bbmax[i] = -INFINITY;
+ }
+
+ for (i = 0; i < info->loopcount; i++) {
+ if (bp + 50 > end)
+ PARSE_FAIL("Truncated data! (loops missing)\n");
+ bp += 12;
+ for (k = 0; k < 3; k++, bp += 12) {
+ for (m = 0; m < 3; m++) {
+ v = fle32toh(*(float*)(bp + 4 * m));
+ info->bbmin[m] = MIN(info->bbmin[m], v);
+ info->bbmax[m] = MAX(info->bbmax[m], v);
+ }
+ }
+ bp += 2;
+ }
+
+ return OK;
+
+fail:
+ return FAIL;
+}
+
+int
+parse_file(struct parseinfo *info, char *buf, size_t len)
+{
+ int status;
+ const char *resp;
+ char *bp;
+
+ if (info->valid) free_info(info);
+
+ if (len < 7) {
+ fprintf(stderr, "File too small!\n");
+ return FAIL;
+ }
+
+ info->filesize = len;
+
+ /* check bin vs ascii with first keyword */
+ for (bp = buf; isws(*bp); bp++);
+ status = !strncmp("solid", bp, 5) && isws(bp[5])
+ ? parse_file_ascii(info, buf, len)
+ : parse_file_bin(info, buf, len);
+ if (status == FAIL) return FAIL;
+
+ if (!info->solidname) info->solidname = checkp(strdup(""));
+
+ if (!info->modelname) {
+ resp = ask("Please enter your model name: ");
+ if (strlen(resp) < 4) {
+ fprintf(stderr, "Model name is too short!\n");
+ return FAIL;
+ }
+ info->modelname = checkp(strdup(resp));
+ }
+
+ info->hash = checkp(strdup(mhash(info->modelname, -1)));
+
+ return OK;
+}
+
+int
+save_info(struct parseinfo *info, FILE *f)
+{
+ size_t nwrote = 0;
+ int i;
+
+ nwrote += fwrite(&info->type, sizeof(int), 1, f);
+ nwrote += fwrite(&info->loopcount, sizeof(int), 1, f);
+ nwrote += fwrite(&info->filesize, sizeof(unsigned), 1, f);
+
+ for (i = 0; i < 3; i++) {
+ nwrote += fwrite(&info->bbmin[i], sizeof(float), 1, f);
+ nwrote += fwrite(&info->bbmax[i], sizeof(float), 1, f);
+ }
+
+ nwrote += fwrite(info->header, 80, 1, f);
+
+ if (nwrote != 10) return FAIL;
+
+ fputstr(f, info->solidname);
+ fputstr(f, info->hash);
+ fputstr(f, info->modelname);
+
+ return OK;
+}
+
+int
+load_info(struct parseinfo *info, FILE *f)
+{
+ size_t nread = 0;
+ int i;
+
+ nread += fread(&info->type, sizeof(int), 1, f);
+ nread += fread(&info->loopcount, sizeof(int), 1, f);
+ nread += fread(&info->filesize, sizeof(unsigned), 1, f);
+
+ for (i = 0; i < 3; i++) {
+ nread += fread(&info->bbmin[i], sizeof(float), 1, f);
+ nread += fread(&info->bbmax[i], sizeof(float), 1, f);
+ }
+
+ nread += fread(info->header, 80, 1, f);
+
+ if (nread != 10) return FAIL;
+
+ freadstr(f, &info->solidname);
+ freadstr(f, &info->hash);
+ freadstr(f, &info->modelname);
+
+ info->valid = 1;
+
+ return OK;
+}
+
+void
+print_info(struct parseinfo *info)
+{
+ int i, k;
+
+#define FILTERCHAR(c) ((c) >= 32 ? (c) : ' ')
+
+ printf(" === Model info === \n");
+
+ printf(" File Size: %u\n", info->filesize);
+
+ if (info->type == TYPE_BIN) {
+ printf(" Header:\n");
+ for (i = 0; i < 80; i += k) {
+ printf(" ");
+ for (k = 0; k < MIN(80 - i, 20); k++)
+ printf(" %02x", (uint8_t) info->header[i+k]);
+ printf(" | ");
+ for (k = 0; k < MIN(80 - i, 20); k++)
+ printf("%c", FILTERCHAR(info->header[i+k]));
+ printf("\n");
+ }
+ }
+
+ printf(" Model ID: %s\n", info->hash);
+ printf(" Model Name: %s\n", info->modelname);
+ printf(" Solid Name: %s\n", info->solidname);
+ printf(" Triangle Count: %i\n", info->loopcount);
+ printf(" Bounding Box Size: %.2f x %.2f x %.2f\n",
+ info->bbmax[0] - info->bbmin[0],
+ info->bbmax[1] - info->bbmin[1],
+ info->bbmax[2] - info->bbmin[2]);
+ printf(" Bounding Box Origin: %.2f x %.2f x %.2f\n",
+ info->bbmin[0], info->bbmin[1], info->bbmin[2]);
+
+ printf(" ================== \n");
+}
+
+void
+free_info(struct parseinfo *info)
+{
+ NULLFREE(info->hash);
+ NULLFREE(info->modelname);
+ NULLFREE(info->solidname);
+ info->valid = 0;
+}
+
+float fle32toh(float v)
+{
+ union {
+ uint32_t u;
+ float f;
+ } conv;
+
+ conv.f = v;
+ conv.u = le32toh(conv.u);
+ return conv.f;
+}