def _add_aligned_cuboid_to_model(minx, maxx, miny, maxy, minz, maxz): points = ((minx, miny, minz), (maxx, miny, minz), (maxx, maxy, minz), (minx, maxy, minz), (minx, miny, maxz), (maxx, miny, maxz), (maxx, maxy, maxz), (minx, maxy, maxz)) triangles = [] # lower face triangles.extend( _get_triangles_for_face((points[0], points[1], points[2], points[3]))) # upper face triangles.extend( _get_triangles_for_face((points[7], points[6], points[5], points[4]))) # front face triangles.extend( _get_triangles_for_face((points[0], points[4], points[5], points[1]))) # back face triangles.extend( _get_triangles_for_face((points[2], points[6], points[7], points[3]))) # right face triangles.extend( _get_triangles_for_face((points[1], points[5], points[6], points[2]))) # left face triangles.extend( _get_triangles_for_face((points[3], points[7], points[4], points[0]))) # add all triangles to the model model = Model() for t in triangles: model.append(t) return model
def _add_aligned_cuboid_to_model(minx, maxx, miny, maxy, minz, maxz): points = ( Point(minx, miny, minz), Point(maxx, miny, minz), Point(maxx, maxy, minz), Point(minx, maxy, minz), Point(minx, miny, maxz), Point(maxx, miny, maxz), Point(maxx, maxy, maxz), Point(minx, maxy, maxz)) triangles = [] # lower face triangles.extend(_get_triangles_for_face( (points[0], points[1], points[2], points[3]))) # upper face triangles.extend(_get_triangles_for_face( (points[7], points[6], points[5], points[4]))) # front face triangles.extend(_get_triangles_for_face( (points[0], points[4], points[5], points[1]))) # back face triangles.extend(_get_triangles_for_face( (points[2], points[6], points[7], points[3]))) # right face triangles.extend(_get_triangles_for_face( (points[1], points[5], points[6], points[2]))) # left face triangles.extend(_get_triangles_for_face( (points[3], points[7], points[4], points[0]))) # add all triangles to the model model = Model() for t in triangles: model.append(t) return model
def get_test_model(): points = [] points.append(Point(-2, 1, 4)) points.append(Point(2, 1, 4)) points.append(Point(0, -2, 4)) points.append(Point(-5, 2, 2)) points.append(Point(-1, 3, 2)) points.append(Point(5, 2, 2)) points.append(Point(4, -1, 2)) points.append(Point(2, -4, 2)) points.append(Point(-2, -4, 2)) points.append(Point(-3, -2, 2)) lines = [] lines.append(Line(points[0], points[1])) lines.append(Line(points[1], points[2])) lines.append(Line(points[2], points[0])) lines.append(Line(points[0], points[3])) lines.append(Line(points[3], points[4])) lines.append(Line(points[4], points[0])) lines.append(Line(points[4], points[1])) lines.append(Line(points[4], points[5])) lines.append(Line(points[5], points[1])) lines.append(Line(points[5], points[6])) lines.append(Line(points[6], points[1])) lines.append(Line(points[6], points[2])) lines.append(Line(points[6], points[7])) lines.append(Line(points[7], points[2])) lines.append(Line(points[7], points[8])) lines.append(Line(points[8], points[2])) lines.append(Line(points[8], points[9])) lines.append(Line(points[9], points[2])) lines.append(Line(points[9], points[0])) lines.append(Line(points[9], points[3])) model = Model() for p1, p2, p3, l1, l2, l3 in ( (0, 1, 2, 0, 1, 2), (0, 3, 4, 3, 4, 5), (0, 4, 1, 5, 6, 0), (1, 4, 5, 6, 7, 8), (1, 5, 6, 8, 9, 10), (1, 6, 2, 10, 11, 1), (2, 6, 7, 11, 12, 13), (2, 7, 8, 13, 14, 15), (2, 8, 9, 15, 16, 17), (2, 9, 0, 17, 18, 2), (0, 9, 3, 18, 19, 3)): model.append(Triangle(points[p1], points[p2], points[p3])) return model
"definition %d of '%s'. Please validate the " + \ "STL file!") % (i, filename)) normal_conflict_warning_seen = True t = Triangle(p1, p2, p3) else: # the three points are in a line - or two points are identical # usually this is caused by points, that are too close together # check the tolerance value in pycam/Geometry/PointKdtree.py log.warn("Skipping invalid triangle: %s / %s / %s " \ % (p1, p2, p3) + "(maybe the resolution of the model " \ + "is too high?)") continue if n: t.normal = n model.append(t) else: solid = re.compile(r"\s*solid\s+(\w+)\s+.*") endsolid = re.compile(r"\s*endsolid\s*") facet = re.compile(r"\s*facet\s*") normal = re.compile(r"\s*facet\s+normal" \ + r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" \ + r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" \ + r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+") endfacet = re.compile(r"\s*endfacet\s+") loop = re.compile(r"\s*outer\s+loop\s+") endloop = re.compile(r"\s*endloop\s+") vertex = re.compile(r"\s*vertex" \ + r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" \ + r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" \ + r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+")
def import_model(filename, use_kdtree=True, callback=None, **kwargs): global vertices, edges, kdtree vertices = 0 edges = 0 kdtree = None normal_conflict_warning_seen = False if hasattr(filename, "read"): # make sure that the input stream can seek and has ".len" f = BufferedReader(filename) # useful for later error messages filename = "input stream" else: try: url_file = pycam.Utils.URIHandler(filename).open() # urllib.urlopen objects do not support "seek" - so we need a buffered reader # Is there a better approach than consuming the whole file at once? f = BufferedReader(BytesIO(url_file.read())) url_file.close() except IOError as exc: raise LoadFileError( "STLImporter: Failed to read file ({}): {}".format( filename, exc)) # the facet count is only available for the binary format facet_count = get_facet_count_if_binary_format(f) is_binary = (facet_count is not None) if use_kdtree: kdtree = PointKdtree([], 3, 1, epsilon) model = Model(use_kdtree) t = None p1 = None p2 = None p3 = None if is_binary: # Skip the header and count fields of binary stl file f.seek(HEADER_SIZE + COUNT_SIZE) for i in range(1, facet_count + 1): if callback and callback(): raise AbortOperationException( "STLImporter: load model operation cancelled") a1 = unpack("<f", f.read(4))[0] a2 = unpack("<f", f.read(4))[0] a3 = unpack("<f", f.read(4))[0] n = (float(a1), float(a2), float(a3), 'v') v11 = unpack("<f", f.read(4))[0] v12 = unpack("<f", f.read(4))[0] v13 = unpack("<f", f.read(4))[0] p1 = get_unique_vertex(float(v11), float(v12), float(v13)) v21 = unpack("<f", f.read(4))[0] v22 = unpack("<f", f.read(4))[0] v23 = unpack("<f", f.read(4))[0] p2 = get_unique_vertex(float(v21), float(v22), float(v23)) v31 = unpack("<f", f.read(4))[0] v32 = unpack("<f", f.read(4))[0] v33 = unpack("<f", f.read(4))[0] p3 = get_unique_vertex(float(v31), float(v32), float(v33)) # not used (additional attributes) f.read(2) dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1))) if a1 == a2 == a3 == 0: dotcross = pcross(psub(p2, p1), psub(p3, p1))[2] n = None if dotcross > 0: # Triangle expects the vertices in clockwise order t = Triangle(p1, p3, p2) elif dotcross < 0: if not normal_conflict_warning_seen: log.warn( "Inconsistent normal/vertices found in facet definition %d of '%s'. " "Please validate the STL file!", i, filename) normal_conflict_warning_seen = True t = Triangle(p1, p2, p3) else: # the three points are in a line - or two points are identical # usually this is caused by points, that are too close together # check the tolerance value in pycam/Geometry/PointKdtree.py log.warn( "Skipping invalid triangle: %s / %s / %s (maybe the resolution of the " "model is too high?)", p1, p2, p3) continue if n: t.normal = n model.append(t) else: # from here on we want to use a text based input stream (not bytes) f = TextIOWrapper(f, encoding="utf-8") solid = re.compile(r"\s*solid\s+(\w+)\s+.*") endsolid = re.compile(r"\s*endsolid\s*") facet = re.compile(r"\s*facet\s*") normal = re.compile( r"\s*facet\s+normal" + r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+") endfacet = re.compile(r"\s*endfacet\s+") loop = re.compile(r"\s*outer\s+loop\s+") endloop = re.compile(r"\s*endloop\s+") vertex = re.compile( r"\s*vertex" + r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+") current_line = 0 for line in f: if callback and callback(): raise AbortOperationException( "STLImporter: load model operation cancelled") current_line += 1 m = solid.match(line) if m: model.name = m.group(1) continue m = facet.match(line) if m: m = normal.match(line) if m: n = (float(m.group('x')), float(m.group('y')), float(m.group('z')), 'v') else: n = None continue m = loop.match(line) if m: continue m = vertex.match(line) if m: p = get_unique_vertex(float(m.group('x')), float(m.group('y')), float(m.group('z'))) if p1 is None: p1 = p elif p2 is None: p2 = p elif p3 is None: p3 = p else: log.error( "STLImporter: more then 3 points in facet (line %d)", current_line) continue m = endloop.match(line) if m: continue m = endfacet.match(line) if m: if None in (p1, p2, p3): log.warn( "Invalid facet definition in line %d of '%s'. Please validate the " "STL file!", current_line, filename) n, p1, p2, p3 = None, None, None, None continue if not n: n = pnormalized(pcross(psub(p2, p1), psub(p3, p1))) # validate the normal # The three vertices of a triangle in an STL file are supposed # to be in counter-clockwise order. This should match the # direction of the normal. if n is None: # invalid triangle (zero-length vector) dotcross = 0 else: # make sure the points are in ClockWise order dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1))) if dotcross > 0: # Triangle expects the vertices in clockwise order t = Triangle(p1, p3, p2, n) elif dotcross < 0: if not normal_conflict_warning_seen: log.warn( "Inconsistent normal/vertices found in line %d of '%s'. Please " "validate the STL file!", current_line, filename) normal_conflict_warning_seen = True t = Triangle(p1, p2, p3, n) else: # The three points are in a line - or two points are # identical. Usually this is caused by points, that are too # close together. Check the tolerance value in # pycam/Geometry/PointKdtree.py. log.warn( "Skipping invalid triangle: %s / %s / %s (maybe the resolution of " "the model is too high?)", p1, p2, p3) n, p1, p2, p3 = (None, None, None, None) continue n, p1, p2, p3 = (None, None, None, None) model.append(t) continue m = endsolid.match(line) if m: continue # TODO display unique vertices and edges count - currently not counted log.info("Imported STL model: %d triangles", len(model.triangles())) vertices = 0 edges = 0 kdtree = None if not model: # no valid items added to the model raise LoadFileError( "Failed to load model from STL file: no elements found") else: return model
def ImportModel(filename, use_kdtree=True, callback=None, **kwargs): global vertices, edges, kdtree vertices = 0 edges = 0 kdtree = None normal_conflict_warning_seen = False if hasattr(filename, "read"): # make sure that the input stream can seek and has ".len" f = StringIO(filename.read()) # useful for later error messages filename = "input stream" else: try: url_file = pycam.Utils.URIHandler(filename).open() # urllib.urlopen objects do not support "seek" - so we need to read # the whole file at once. This is ugly - anyone with a better idea? f = StringIO(url_file.read()) # TODO: the above ".read" may be incomplete - this is ugly # see http://patrakov.blogspot.com/2011/03/case-of-non-raised-exception.html # and http://stackoverflow.com/questions/1824069/ url_file.close() except IOError as err_msg: log.error("STLImporter: Failed to read file (%s): %s", filename, err_msg) return None # Read the first two lines of (potentially non-binary) input - they should # contain "solid" and "facet". header_lines = [] while len(header_lines) < 2: line = f.readline(200) if len(line) == 0: # empty line (not even a line-feed) -> EOF log.error("STLImporter: No valid lines found in '%s'", filename) return None # ignore comment lines # note: partial comments (starting within a line) are not handled if not line.startswith(";"): header_lines.append(line) header = "".join(header_lines) # read byte 80 to 83 - they contain the "numfacets" value in binary format f.seek(80) numfacets = unpack("<I", f.read(4))[0] binary = False log.debug("STL import info: %s / %s / %s / %s", f.len, numfacets, header.find("solid"), header.find("facet")) if f.len == (84 + 50 * numfacets): binary = True elif header.find("solid") >= 0 and header.find("facet") >= 0: binary = False f.seek(0) else: log.error("STLImporter: STL binary/ascii detection failed") return None if use_kdtree: kdtree = PointKdtree([], 3, 1, epsilon) model = Model(use_kdtree) t = None p1 = None p2 = None p3 = None if binary: for i in range(1, numfacets + 1): if callback and callback(): log.warn("STLImporter: load model operation cancelled") return None a1 = unpack("<f", f.read(4))[0] a2 = unpack("<f", f.read(4))[0] a3 = unpack("<f", f.read(4))[0] n = (float(a1), float(a2), float(a3), 'v') v11 = unpack("<f", f.read(4))[0] v12 = unpack("<f", f.read(4))[0] v13 = unpack("<f", f.read(4))[0] p1 = UniqueVertex(float(v11), float(v12), float(v13)) v21 = unpack("<f", f.read(4))[0] v22 = unpack("<f", f.read(4))[0] v23 = unpack("<f", f.read(4))[0] p2 = UniqueVertex(float(v21), float(v22), float(v23)) v31 = unpack("<f", f.read(4))[0] v32 = unpack("<f", f.read(4))[0] v33 = unpack("<f", f.read(4))[0] p3 = UniqueVertex(float(v31), float(v32), float(v33)) # not used (additional attributes) f.read(2) dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1))) if a1 == a2 == a3 == 0: dotcross = pcross(psub(p2, p1), psub(p3, p1))[2] n = None if dotcross > 0: # Triangle expects the vertices in clockwise order t = Triangle(p1, p3, p2) elif dotcross < 0: if not normal_conflict_warning_seen: log.warn( "Inconsistent normal/vertices found in facet definition %d of '%s'. " "Please validate the STL file!", i, filename) normal_conflict_warning_seen = True t = Triangle(p1, p2, p3) else: # the three points are in a line - or two points are identical # usually this is caused by points, that are too close together # check the tolerance value in pycam/Geometry/PointKdtree.py log.warn( "Skipping invalid triangle: %s / %s / %s (maybe the resolution of the " "model is too high?)", p1, p2, p3) continue if n: t.normal = n model.append(t) else: solid = re.compile(r"\s*solid\s+(\w+)\s+.*") endsolid = re.compile(r"\s*endsolid\s*") facet = re.compile(r"\s*facet\s*") normal = re.compile( r"\s*facet\s+normal" + r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+") endfacet = re.compile(r"\s*endfacet\s+") loop = re.compile(r"\s*outer\s+loop\s+") endloop = re.compile(r"\s*endloop\s+") vertex = re.compile( r"\s*vertex" + r"\s+(?P<x>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<y>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)" + r"\s+(?P<z>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s+") current_line = 0 for line in f: if callback and callback(): log.warn("STLImporter: load model operation cancelled") return None current_line += 1 m = solid.match(line) if m: model.name = m.group(1) continue m = facet.match(line) if m: m = normal.match(line) if m: n = (float(m.group('x')), float(m.group('y')), float(m.group('z')), 'v') else: n = None continue m = loop.match(line) if m: continue m = vertex.match(line) if m: p = UniqueVertex(float(m.group('x')), float(m.group('y')), float(m.group('z'))) if p1 is None: p1 = p elif p2 is None: p2 = p elif p3 is None: p3 = p else: log.error( "STLImporter: more then 3 points in facet (line %d)", current_line) continue m = endloop.match(line) if m: continue m = endfacet.match(line) if m: if None in (p1, p2, p3): log.warn( "Invalid facet definition in line %d of '%s'. Please validate the " "STL file!", current_line, filename) n, p1, p2, p3 = None, None, None, None continue if not n: n = pnormalized(pcross(psub(p2, p1), psub(p3, p1))) # validate the normal # The three vertices of a triangle in an STL file are supposed # to be in counter-clockwise order. This should match the # direction of the normal. if n is None: # invalid triangle (zero-length vector) dotcross = 0 else: # make sure the points are in ClockWise order dotcross = pdot(n, pcross(psub(p2, p1), psub(p3, p1))) if dotcross > 0: # Triangle expects the vertices in clockwise order t = Triangle(p1, p3, p2, n) elif dotcross < 0: if not normal_conflict_warning_seen: log.warn( "Inconsistent normal/vertices found in line %d of '%s'. Please " "validate the STL file!", current_line, filename) normal_conflict_warning_seen = True t = Triangle(p1, p2, p3, n) else: # The three points are in a line - or two points are # identical. Usually this is caused by points, that are too # close together. Check the tolerance value in # pycam/Geometry/PointKdtree.py. log.warn( "Skipping invalid triangle: %s / %s / %s (maybe the resolution of " "the model is too high?)", p1, p2, p3) n, p1, p2, p3 = (None, None, None, None) continue n, p1, p2, p3 = (None, None, None, None) model.append(t) continue m = endsolid.match(line) if m: continue log.info("Imported STL model: %d vertices, %d edges, %d triangles", vertices, edges, len(model.triangles())) vertices = 0 edges = 0 kdtree = None if not model: # no valid items added to the model return None else: return model