diff --git a/README.md b/README.md index e69de29..fde3a89 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,11 @@ +# OpenSCAD Examples + +Examples and exercises for comparing features and techniques in using OpenSCAD. + +## Tests + +### Deterministically-ordered STL Output + +```bash +bash deterministic_tests.sh +``` \ No newline at end of file diff --git a/deterministic_tests.sh b/deterministic_tests.sh new file mode 100644 index 0000000..f393344 --- /dev/null +++ b/deterministic_tests.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +############## +# BASELINE +############## + +echo "Baseline..." +openscad --version + +BASELINE=$(mktemp -d) +FIRST="${BASELINE}/first.stl" +SECOND="${BASELINE}/second.stl" + +openscad -q -o "${FIRST}" scad/deterministic.scad +openscad -q -o "${SECOND}" scad/deterministic2.scad + +DIFF_VAL=$(diff "${FIRST}" "${SECOND}") + +if [[ "${DIFF_VAL}" == "" ]]; then + echo "Files are the same" +else + #echo "${DIFF_VAL}" + echo "Files are different" +fi + +rm -rf "${BASELINE}" + +############## +# SORT-STL +############## + +echo "sort-stl..." + +SORTSTL=$(mktemp -d) +FIRST="${SORTSTL}/first.stl" +SECOND="${SORTSTL}/second.stl" + +openscad -q -o "${FIRST}" --enable sort-stl scad/deterministic.scad +openscad -q -o "${SECOND}" --enable sort-stl scad/deterministic2.scad + +DIFF_VAL=$(diff "${FIRST}" "${SECOND}") + +if [[ "${DIFF_VAL}" == "" ]]; then + echo "Files are the same" +else + #echo "${DIFF_VAL}" + echo "Files are different" +fi + +rm -rf "${SORTSTL}" + +############## +# C14N +############## + +echo "C14N..." + +C14N=$(mktemp -d) +FIRST="${C14N}/first.stl" +SECOND="${C14N}/second.stl" + +openscad -q -o "${FIRST}" scad/deterministic.scad +python3 python/c14n_stl.py "${FIRST}" +openscad -q -o "${SECOND}" scad/deterministic2.scad +python3 python/c14n_stl.py "${SECOND}" + +DIFF_VAL=$(diff "${FIRST}" "${SECOND}") + +if [[ "${DIFF_VAL}" == "" ]]; then + echo "Files are the same" +else + #echo "${DIFF_VAL}" + echo "Files are different" +fi + +rm -rf "${C14N}" + +#--export-format asciistl +#--export-format binstl diff --git a/python/c14n_stl.py b/python/c14n_stl.py new file mode 100644 index 0000000..9ba5d4d --- /dev/null +++ b/python/c14n_stl.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +# +# NopSCADlib Copyright Chris Palmer 2018 +# nop.head@gmail.com +# hydraraptor.blogspot.com +# +# This file is part of NopSCADlib. +# +# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# NopSCADlib 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with NopSCADlib. +# If not, see . +# + +# +#! OpenSCAD produces randomly ordered STL files. This script re-orders them consistently so that GIT can tell if they have changed or not. +# +# OpenSCAD produces randomly ordered STL files so source control like GIT can't tell if they have changed or not. +# This scrip orders each triangle to start with the lowest vertex first (comparing x, then y, then z) +# It then sorts the triangles to start with the one with the lowest vertices first (comparing first vertex, second, then third) +# This has no effect on the model but makes the STL consistent. I.e. it makes a canonical form. +# + +from __future__ import print_function + +import sys + +def cmz(x): + ''' Convert "-0" to "0". ''' + return '0' if x == '-0' else x + +class Vertex: + def __init__(self, x, y, z): + self.x, self.y, self.z = x, y, z + self.key = (float(x), float(y), float(z)) + +class Normal: + def __init__(self, dx, dy, dz): + self.dx, self.dy, self.dz = dx, dy, dz + +class Facet: + def __init__(self, normal, v1, v2, v3): + self.normal = normal + if v1.key < v2.key: + if v1.key < v3.key: + self.vertices = (v1, v2, v3) #v1 is the smallest + else: + self.vertices = (v3, v1, v2) #v3 is the smallest + else: + if v2.key < v3.key: + self.vertices = (v2, v3, v1) #v2 is the smallest + else: + self.vertices = (v3, v1, v2) #v3 is the smallest + + def key(self): + return (self.vertices[0].x, self.vertices[0].y, self.vertices[0].z, + self.vertices[1].x, self.vertices[1].y, self.vertices[1].z, + self.vertices[2].x, self.vertices[2].y, self.vertices[2].z) + +class STL: + def __init__(self, fname): + self.facets = [] + + with open(fname) as f: + words = [cmz(s.strip()) for s in f.read().split()] + + if words[0] == 'solid' and words[1] == 'OpenSCAD_Model': + i = 2 + while words[i] == 'facet': + norm = Normal(words[i + 2], words[i + 3], words[i + 4]) + v1 = Vertex(words[i + 8], words[i + 9], words[i + 10]) + v2 = Vertex(words[i + 12], words[i + 13], words[i + 14]) + v3 = Vertex(words[i + 16], words[i + 17], words[i + 18]) + i += 21 + self.facets.append(Facet(norm, v1, v2, v3)) + + self.facets.sort(key = Facet.key) + else: + print("Not an OpenSCAD ascii STL file") + sys.exit(1) + + def write(self, fname): + mins = [float('inf'), float('inf'), float('inf')] + maxs = [float('-inf'), float('-inf'), float('-inf')] + with open(fname,"wt") as f: + print('solid OpenSCAD_Model', file=f) + for facet in self.facets: + print(' facet normal %s %s %s' % (facet.normal.dx, facet.normal.dy, facet.normal.dz), file=f) + print(' outer loop', file=f) + for vertex in facet.vertices: + print(' vertex %s %s %s' % (vertex.x, vertex.y, vertex.z), file=f) + for i in range(3): + ordinate = vertex.key[i] + if ordinate > maxs[i]: maxs[i] = ordinate + if ordinate < mins[i]: mins[i] = ordinate + print(' endloop', file=f) + print(' endfacet', file=f) + print('endsolid OpenSCAD_Model', file=f) + return mins, maxs + +def canonicalise(fname): + stl = STL(fname) + return stl.write(fname) + +if __name__ == '__main__': + if len(sys.argv) == 2: + canonicalise(sys.argv[1]) + else: + print("\nusage:\n\t c14n_stl file - Canonicalise an STL file created by OpenSCAD.") + sys.exit(1) \ No newline at end of file diff --git a/scad/deterministic.scad b/scad/deterministic.scad new file mode 100644 index 0000000..48e7e1c --- /dev/null +++ b/scad/deterministic.scad @@ -0,0 +1,8 @@ +$fn = 100; + +difference () { + cube([40, 40, 40], center = true); + cylinder(r = 20/2, h = 40 + 1, center = true); + rotate([90, 0, 0]) cylinder(r = 20/2, h = 40 + 1, center = true); + rotate([0, 90, 0]) cylinder(r = 20/2, h = 40 + 1, center = true); +} \ No newline at end of file diff --git a/scad/deterministic2.scad b/scad/deterministic2.scad new file mode 100644 index 0000000..4f51503 --- /dev/null +++ b/scad/deterministic2.scad @@ -0,0 +1,8 @@ +$fn = 100; + +difference () { + cube([40, 40, 40], center = true); + rotate([0, 90, 0]) cylinder(r = 20/2, h = 40 + 1, center = true); + rotate([90, 0, 0]) cylinder(r = 20/2, h = 40 + 1, center = true); + cylinder(r = 20/2, h = 40 + 1, center = true); +} \ No newline at end of file