diff --git a/scripts/c14n_stl.py b/scripts/c14n_stl.py new file mode 100644 index 0000000..9ba5d4d --- /dev/null +++ b/scripts/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