enowars5-service-stldoctor

STL-Analyzing A/D Service for ENOWARS5 in 2021
git clone https://git.sinitax.com/sinitax/enowars5-service-stldoctor
Log | Files | Refs | README | LICENSE | sfeed.txt

commit a0d6bf48a185026589288fd9aa94506b321301d8
parent 93107ebd417e75efed4e2173feeea1030ce6cd02
Author: Louis Burda <quent.burda@gmail.com>
Date:   Tue,  4 May 2021 11:07:45 +0200

further improved parsing and related tests

Diffstat:
Mservice/src/msgs/welcome | 2+-
Mservice/src/printdoc.c | 3++-
Mservice/src/stlfile.c | 166++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mservice/src/stlfile.h | 9+++++----
Mservice/src/test.sh | 35++++++++++++++++++++++++++++++++++-
Mservice/src/tests/sample-ascii.stl | 2+-
Aservice/src/tests/sample-binary.stl | 0
7 files changed, 152 insertions(+), 65 deletions(-)

diff --git a/service/src/msgs/welcome b/service/src/msgs/welcome @@ -1,2 +1,2 @@ Welcome to PrintDoc! -Upload stl files and see all the interesting details! +Upload an stl file and we'll analyze it! diff --git a/service/src/printdoc.c b/service/src/printdoc.c @@ -138,7 +138,7 @@ submit_cmd(char *arg) lastrun.valid = parse_file(&lastrun, contents, len); - // if (lastrun.valid) dump(lastrun.infopath); + if (lastrun.valid && lastrun.infopath) dump(lastrun.infopath); free(contents); } @@ -203,4 +203,5 @@ main() } printf("see you later!\n"); + free_info(&lastrun); } diff --git a/service/src/stlfile.c b/service/src/stlfile.c @@ -2,6 +2,18 @@ #include "util.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) @@ -27,7 +39,7 @@ int stack_pop(struct stack *stack) { if (stack->count == 0) - die("popping empty stack!\n"); + return -1; stack->count--; return stack->data[stack->count]; @@ -39,6 +51,16 @@ 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; +} + long skip_set(char *start, const char *set) { @@ -63,101 +85,117 @@ consume_arg(char **start) int consume_keyword(char **start) { - static const struct { - int code; - const char *str; - } mapping[] = { - { 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" }, - }; - char *bp, *nsep; + char *bp; int i, len; bp = *start + skip_set(*start, wsset); - for (i = 0; i < ARRSIZE(mapping); i++) { - len = strlen(mapping[i].str); - if (!strncmp(mapping[i].str, bp, len) && strchr(wsset, *(bp + len))) { - printf("GOT: %s\n", mapping[i].str); + for (i = 0; i < ARRSIZE(kwmap); i++) { + len = strlen(kwmap[i].str); + if (!strncmp(kwmap[i].str, bp, len) && strchr(wsset, *(bp + len))) { + printf("GOT: %s\n", kwmap[i].str); *start = bp + len + (bp[len] ? 1 : 0); - return mapping[i].code; + 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, *nsep, *prev; + char *bp, *arg, *prev, *tmp; struct stack states; + float farg; int i, kw; stack_init(&states); info->type = TYPE_ASCII; + info->loopcount = 0; bp = prev = buf; while ((kw = consume_keyword(&bp))) { switch (kw) { case KW_SOLID_BEGIN: - /* TODO: save solid name */ stack_push(&states, STATE_SOLID); - consume_arg(&bp); + if (stack_ind(&states, KW_SOLID_BEGIN) != -1) + PARSE_FAIL("multiple nested solids!\n"); + tmp = bp; + if (!consume_keyword(&bp) && (arg = consume_arg(&bp))) { + memcpy(info->extra, arg, MIN(strlen(arg), 80)); + } else { + bp = tmp; + } + info->namehash = checkp(strdup(mhash(info->extra, 80))); break; case KW_SOLID_END: - /* TODO: check that name matches */ - if (stack_pop(&states) != STATE_SOLID) - goto unknown; - consume_arg(&bp); + if ((kw = stack_pop(&states)) != STATE_SOLID) + PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str); + tmp = bp; + if (*info->extra && !consume_keyword(&bp) + && (arg = consume_arg(&bp))) { + if (strncmp(info->extra, arg, 80)) + PARSE_FAIL("solid end/begin names do not match!\n"); + } else { + bp = tmp; + } break; case KW_LOOP_BEGIN: - /* TODO: ensure only one loop */ 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 (stack_pop(&states) != STATE_LOOP) - goto unknown; + if ((kw = stack_pop(&states)) != STATE_LOOP) + PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str); + info->loopcount++; break; case KW_FACET_BEGIN: - /* TODO: check if args are integers */ stack_push(&states, STATE_FACET); - for (i = 0; i < 3 && consume_arg(&bp); i++); + 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))) + PARSE_FAIL("Facet with less than 3 args!\n"); + farg = strtof(arg, &tmp); + if (tmp && *tmp) + PARSE_FAIL("Facet with invalid arg '%s'!\n", arg); + } break; case KW_FACET_END: - if (stack_pop(&states) != STATE_FACET) - goto unknown; + if ((kw = stack_pop(&states)) != STATE_FACET) + PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str); break; case KW_VERTEX: - /* TODO: check if args are integers */ - for (i = 0; i < 3 && consume_arg(&bp); i++); + /* TODO: calc bounding box */ + for (i = 0; i < 3; i++) { + if (!(arg = consume_arg(&bp))) + PARSE_FAIL("Vertex with less than 3 args!\n"); + farg = strtof(arg, &tmp); + if (tmp && *tmp) + PARSE_FAIL("Vertex with invalid arg '%s'!\n", arg); + } break; -unknown: case KW_UNKNOWN: prev += skip_set(prev, wsset); - fprintf(stderr, "Expected keyword, got:\n%.*s...\n", 30, prev); - return FAIL; + PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, prev); } prev = bp; } - if (states.count) { - fprintf(stderr, "Expected keyword, got:\n%.*s...\n", 30, bp); - return FAIL; - } + if (states.count) + PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp); stack_free(&states); return OK; fail: stack_free(&states); - free(info); return FAIL; } @@ -167,10 +205,10 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len) char *bp, *end = buf + len; unsigned int i; - if (len < 84) { - fprintf(stderr, "Truncated data! (header missing)\n"); - return FAIL; - } + info->type = TYPE_BIN; + + if (len < 84) + PARSE_FAIL("Truncated data! (header missing)\n"); /* treat header as model name */ info->namehash = strdup(mhash(buf, 80)); @@ -179,31 +217,45 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len) info->loopcount = le32toh(*(uint32_t*)bp); for (i = 0; i < info->loopcount; i++) { - if (bp + 50 > end) { - fprintf(stderr, "Truncated data! (loops missing)\n"); - return FAIL; - } + if (bp + 50 > end) + PARSE_FAIL("Truncated data! (loops missing)\n"); + /* TODO: load floats and calc bounding box */ bp += 50; } return OK; + +fail: + return FAIL; } int parse_file(struct parseinfo *info, char *buf, size_t len) { + int status; char *bp; - if (info->valid) { - NULLFREE(info->infopath); - NULLFREE(info->namehash); - NULLFREE(info->stlpath); - } + if (info->valid) + free_info(info); /* check bin vs ascii */ for (bp = buf; strchr(wsset, *bp); bp++); - return !strncmp("solid ", bp, 6) + status = !strncmp("solid ", bp, 6) ? parse_file_ascii(info, buf, len) : parse_file_bin(info, buf, len); + if (status == FAIL) return FAIL; + + printf("LOOPS: %i\n", info->loopcount); + /* TODO: create new dir and write parseinfo to files */ + + return OK; +} + +void +free_info(struct parseinfo *info) +{ + NULLFREE(info->namehash); + NULLFREE(info->stlpath); + NULLFREE(info->infopath); } diff --git a/service/src/stlfile.h b/service/src/stlfile.h @@ -7,6 +7,7 @@ #include <endian.h> enum { + KW_INVALID = -1, KW_UNKNOWN, KW_SOLID_BEGIN, KW_SOLID_END, @@ -34,14 +35,14 @@ struct stack { }; struct parseinfo { - char fmtbuf[256]; - char header[80]; - int type, valid; + char extra[80]; unsigned int loopcount; - float bbw, bbh; + float bbmin[3], bbmax[3]; + int type, valid; char *namehash, *infopath, *stlpath; }; int parse_file(struct parseinfo *info, char *buf, size_t len); +void free_info(struct parseinfo *info); #endif /* STLFILE_H */ diff --git a/service/src/test.sh b/service/src/test.sh @@ -1,9 +1,42 @@ #!/bin/sh +set -e + +announce() { + count=$(echo "$1" | wc -c) + python3 -c " +import math +s = '$1' +c = 80 +print() +print('#'*c) +print('#' + ' '*math.floor((c - len(s))/2-1) + s + ' '*math.ceil((c - len(s))/2-1) + '#') +print('#'*c) +print() + " +} + +checkleaks() { + valgrind --leak-check=full ./printdoc 2>&1 | tee /tmp/testlog + if [ -z "$(grep "no leaks are possible" /tmp/testlog)" ]; then + echo "Valgrind exited with errors!" + exit 1 + fi +} + +announce "Trying ASCII STL" ( echo "help" echo "submit" cat tests/sample-ascii.stl | wc -c cat tests/sample-ascii.stl -) | valgrind --leak-check=full ./printdoc +) | checkleaks + +announce "Trying BIN STL" +( + echo "help" + echo "submit" + cat tests/sample-binary.stl | wc -c + cat tests/sample-binary.stl +) | checkleaks diff --git a/service/src/tests/sample-ascii.stl b/service/src/tests/sample-ascii.stl @@ -1,5 +1,5 @@ solid test - facet normal 0 0 1 + facet normal 0 0 1.0 outer loop vertex 1 0 0 vertex 1 1 0 diff --git a/service/src/tests/sample-binary.stl b/service/src/tests/sample-binary.stl Binary files differ.