diff -r 51bf56ba3c10 -r 0bbb006204fc printrun-src/printrun/stltool.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/printrun-src/printrun/stltool.py Fri Jun 03 09:16:07 2016 +0200
@@ -0,0 +1,390 @@
+# coding: utf-8
+
+# This file is part of the Printrun suite.
+#
+# Printrun 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.
+#
+# Printrun 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 Printrun. If not, see .
+
+import sys
+import struct
+import math
+import logging
+
+import numpy
+import numpy.linalg
+
+def normalize(v):
+ return v / numpy.linalg.norm(v)
+
+def genfacet(v):
+ veca = v[1] - v[0]
+ vecb = v[2] - v[1]
+ vecx = numpy.cross(veca, vecb)
+ vlen = numpy.linalg.norm(vecx)
+ if vlen == 0:
+ vlen = 1
+ normal = vecx / vlen
+ return (normal, v)
+
+I = numpy.identity(4)
+
+def homogeneous(v, w = 1):
+ return numpy.append(v, w)
+
+def applymatrix(facet, matrix = I):
+ return genfacet(map(lambda x: matrix.dot(homogeneous(x))[:3], facet[1]))
+
+def ray_triangle_intersection(ray_near, ray_dir, (v1, v2, v3)):
+ """
+ Möller–Trumbore intersection algorithm in pure python
+ Based on http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
+ """
+ eps = 0.000001
+ edge1 = v2 - v1
+ edge2 = v3 - v1
+ pvec = numpy.cross(ray_dir, edge2)
+ det = edge1.dot(pvec)
+ if abs(det) < eps:
+ return False, None
+ inv_det = 1. / det
+ tvec = ray_near - v1
+ u = tvec.dot(pvec) * inv_det
+ if u < 0. or u > 1.:
+ return False, None
+ qvec = numpy.cross(tvec, edge1)
+ v = ray_dir.dot(qvec) * inv_det
+ if v < 0. or u + v > 1.:
+ return False, None
+
+ t = edge2.dot(qvec) * inv_det
+ if t < eps:
+ return False, None
+
+ return True, t
+
+def ray_rectangle_intersection(ray_near, ray_dir, p0, p1, p2, p3):
+ match1, _ = ray_triangle_intersection(ray_near, ray_dir, (p0, p1, p2))
+ match2, _ = ray_triangle_intersection(ray_near, ray_dir, (p0, p2, p3))
+ return match1 or match2
+
+def ray_box_intersection(ray_near, ray_dir, p0, p1):
+ x0, y0, z0 = p0[:]
+ x1, y1, z1 = p1[:]
+ rectangles = [((x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)),
+ ((x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)),
+ ((x0, y0, z0), (x1, y0, z0), (x1, y0, z1), (x0, y0, z1)),
+ ((x0, y1, z0), (x1, y1, z0), (x1, y1, z1), (x0, y1, z1)),
+ ((x0, y0, z0), (x0, y1, z0), (x0, y1, z1), (x0, y0, z1)),
+ ((x1, y0, z0), (x1, y1, z0), (x1, y1, z1), (x1, y0, z1)),
+ ]
+ rectangles = [(numpy.array(p) for p in rect)
+ for rect in rectangles]
+ for rect in rectangles:
+ if ray_rectangle_intersection(ray_near, ray_dir, *rect):
+ return True
+ return False
+
+def emitstl(filename, facets = [], objname = "stltool_export", binary = True):
+ if filename is None:
+ return
+ if binary:
+ with open(filename, "wb") as f:
+ buf = "".join(["\0"] * 80)
+ buf += struct.pack(" maxx:
+ maxx = vert[0]
+ if vert[1] > maxy:
+ maxy = vert[1]
+ if vert[2] > maxz:
+ maxz = vert[2]
+ self._dims = [minx, maxx, miny, maxy, minz, maxz]
+ return self._dims
+ dims = property(_get_dims)
+
+ def __init__(self, filename = None):
+ self.facet = (numpy.zeros(3), (numpy.zeros(3), numpy.zeros(3), numpy.zeros(3)))
+ self.facets = []
+ self.facetsminz = []
+ self.facetsmaxz = []
+
+ self.name = ""
+ self.insolid = 0
+ self.infacet = 0
+ self.inloop = 0
+ self.facetloc = 0
+ if filename is None:
+ return
+ with open(filename) as f:
+ data = f.read()
+ if "facet normal" in data[1:300] and "outer loop" in data[1:300]:
+ lines = data.split("\n")
+ for line in lines:
+ if not self.parseline(line):
+ return
+ else:
+ logging.warning("Not an ascii stl solid - attempting to parse as binary")
+ f = open(filename, "rb")
+ buf = f.read(84)
+ while len(buf) < 84:
+ newdata = f.read(84 - len(buf))
+ if not len(newdata):
+ break
+ buf += newdata
+ facetcount = struct.unpack_from(" 0:
+ e2 = - e2
+ e3 = - e3
+ matrix = [[e1[0], e2[0], e3[0], 0],
+ [e1[1], e2[1], e3[1], 0],
+ [e1[2], e2[2], e3[2], 0],
+ [0, 0, 0, 1]]
+ matrix = numpy.array(matrix)
+ # Inverse change of basis matrix
+ matrix = numpy.linalg.inv(matrix)
+ # Set first vertex of facet as origin
+ neworig = matrix.dot(homogeneous(facet[0]))
+ matrix[:3, 3] = -neworig[:3]
+ newmodel = self.transform(matrix)
+ return newmodel
+
+ def cut(self, axis, direction, dist):
+ s = stl()
+ s.facets = []
+ f = min if direction == 1 else max
+ for _, facet in self.facets:
+ minval = f([vertex[axis] for vertex in facet])
+ if direction * minval > direction * dist:
+ continue
+ vertices = []
+ for vertex in facet:
+ vertex = numpy.copy(vertex)
+ if direction * (vertex[axis] - dist) > 0:
+ vertex[axis] = dist
+ vertices.append(vertex)
+ s.facets.append(genfacet(vertices))
+ s.insolid = 0
+ s.infacet = 0
+ s.inloop = 0
+ s.facetloc = 0
+ s.name = self.name
+ for facet in s.facets:
+ s.facetsminz += [(min(map(lambda x:x[2], facet[1])), facet)]
+ s.facetsmaxz += [(max(map(lambda x:x[2], facet[1])), facet)]
+ return s
+
+ def translation_matrix(self, v):
+ matrix = [[1, 0, 0, v[0]],
+ [0, 1, 0, v[1]],
+ [0, 0, 1, v[2]],
+ [0, 0, 0, 1]
+ ]
+ return numpy.array(matrix)
+
+ def translate(self, v = [0, 0, 0]):
+ return self.transform(self.translation_matrix(v))
+
+ def rotation_matrix(self, v):
+ z = v[2]
+ matrix1 = [[math.cos(math.radians(z)), -math.sin(math.radians(z)), 0, 0],
+ [math.sin(math.radians(z)), math.cos(math.radians(z)), 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, 0, 1]
+ ]
+ matrix1 = numpy.array(matrix1)
+ y = v[0]
+ matrix2 = [[1, 0, 0, 0],
+ [0, math.cos(math.radians(y)), -math.sin(math.radians(y)), 0],
+ [0, math.sin(math.radians(y)), math.cos(math.radians(y)), 0],
+ [0, 0, 0, 1]
+ ]
+ matrix2 = numpy.array(matrix2)
+ x = v[1]
+ matrix3 = [[math.cos(math.radians(x)), 0, -math.sin(math.radians(x)), 0],
+ [0, 1, 0, 0],
+ [math.sin(math.radians(x)), 0, math.cos(math.radians(x)), 0],
+ [0, 0, 0, 1]
+ ]
+ matrix3 = numpy.array(matrix3)
+ return matrix3.dot(matrix2.dot(matrix1))
+
+ def rotate(self, v = [0, 0, 0]):
+ return self.transform(self.rotation_matrix(v))
+
+ def scale_matrix(self, v):
+ matrix = [[v[0], 0, 0, 0],
+ [0, v[1], 0, 0],
+ [0, 0, v[2], 0],
+ [0, 0, 0, 1]
+ ]
+ return numpy.array(matrix)
+
+ def scale(self, v = [0, 0, 0]):
+ return self.transform(self.scale_matrix(v))
+
+ def transform(self, m = I):
+ s = stl()
+ s.facets = [applymatrix(i, m) for i in self.facets]
+ s.insolid = 0
+ s.infacet = 0
+ s.inloop = 0
+ s.facetloc = 0
+ s.name = self.name
+ for facet in s.facets:
+ s.facetsminz += [(min(map(lambda x:x[2], facet[1])), facet)]
+ s.facetsmaxz += [(max(map(lambda x:x[2], facet[1])), facet)]
+ return s
+
+ def export(self, f = sys.stdout):
+ f.write("solid " + self.name + "\n")
+ for i in self.facets:
+ f.write(" facet normal " + " ".join(map(str, i[0])) + "\n")
+ f.write(" outer loop" + "\n")
+ for j in i[1]:
+ f.write(" vertex " + " ".join(map(str, j)) + "\n")
+ f.write(" endloop" + "\n")
+ f.write(" endfacet" + "\n")
+ f.write("endsolid " + self.name + "\n")
+ f.flush()
+
+ def parseline(self, l):
+ l = l.strip()
+ if l.startswith("solid"):
+ self.insolid = 1
+ self.name = l[6:]
+ elif l.startswith("endsolid"):
+ self.insolid = 0
+ return 0
+ elif l.startswith("facet normal"):
+ l = l.replace(", ", ".")
+ self.infacet = 1
+ self.facetloc = 0
+ normal = numpy.array(map(float, l.split()[2:]))
+ self.facet = (normal, (numpy.zeros(3), numpy.zeros(3), numpy.zeros(3)))
+ elif l.startswith("endfacet"):
+ self.infacet = 0
+ self.facets.append(self.facet)
+ facet = self.facet
+ self.facetsminz += [(min(map(lambda x:x[2], facet[1])), facet)]
+ self.facetsmaxz += [(max(map(lambda x:x[2], facet[1])), facet)]
+ elif l.startswith("vertex"):
+ l = l.replace(", ", ".")
+ self.facet[1][self.facetloc][:] = numpy.array(map(float, l.split()[1:]))
+ self.facetloc += 1
+ return 1
+
+if __name__ == "__main__":
+ s = stl("../../Downloads/frame-vertex-neo-foot-x4.stl")
+ for i in xrange(11, 11):
+ working = s.facets[:]
+ for j in reversed(sorted(s.facetsminz)):
+ if j[0] > i:
+ working.remove(j[1])
+ else:
+ break
+ for j in (sorted(s.facetsmaxz)):
+ if j[0] < i:
+ working.remove(j[1])
+ else:
+ break
+
+ print i, len(working)
+ emitstl("../../Downloads/frame-vertex-neo-foot-x4-a.stl", s.facets, "emitted_object")
+# stl("../prusamendel/stl/mendelplate.stl")