class StlModel: def __init__(self, filename): self.filename = filename self.facets = [] self.parse() self.direction = '+Z' self.ex = dict() self.tree = IntervalTree(0) self.sorted_z = [] self.update() def __nonzero__(self): return True def update(self): logging.info("Current scales:") self.ex = self.get_extremal() self.log_scales() logging.info('Making tree for STL-model...') self.make_tree() logging.info('Finished tree.') def make_tree(self): i = 0 self.sorted_z = [] for facet in self.facets: self.sorted_z.append((facet.minz(), i)) self.sorted_z.append((facet.maxz(), i)) i += 1 self.sorted_z.sort() self.tree = IntervalTree(len(self.sorted_z)) for facet in self.facets: l = self.find_z(facet.minz(), True) r = self.find_z(facet.maxz(), False) self.tree.push(l, r - 1, facet) def intersect_facets(self, z): return self.tree.get(self.find_z(z)) #if bot: returns first element >= z # else: returns first element > z def find_z(self, z, bot=False): l = 0 r = len(self.sorted_z) while r > l: m = (r + l) // 2 if bot: if self.sorted_z[m][0] + EPS > z: r = m else: l = m + 1 else: if self.sorted_z[m][0] - EPS > z: r = m else: l = m + 1 # l == r return l def read_facet(self, f): line = f.readline().strip() if line != 'outer loop': raise ValueError('Expected "outer loop", got "%s"' % line) facet = [] line = f.readline().strip() while line != 'endloop': parts = line.split() if parts[0] != 'vertex': raise ValueError('Expected "vertex x y z", got "%s"' % line) facet.append(tuple([float(num) for num in parts[1:]])) line = f.readline().strip() line = f.readline().strip() if line != 'endfacet': raise ValueError('Expected "endfacet", got "%s"' % line) return Facet(Point3(facet[0]), Point3(facet[1]), Point3(facet[2])) def log_scales(self): e = self.ex logging.info('minx = %.3f \t maxx = %.3f' % (e['minx'], e['maxx'])) logging.info('miny = %.3f \t maxy = %.3f' % (e['miny'], e['maxy'])) logging.info('minz = %.3f \t maxz = %.3f' % (e['minz'], e['maxz'])) def parse_text(self): f = open(self.filename, 'r') logging.info('Parsing STL text model') line = f.readline().strip() parts = line.split() if parts[0] != 'solid': raise FormatSTLError('Expected "solid ...", got "%s"' % line) name = ' '.join(parts[1:]) line = f.readline().strip() while line.startswith('facet'): try: facet = self.read_facet(f) self.facets.append(facet) except AssertionError: pass line = f.readline().strip() if line != ('endsolid %s' % name) and line != "endsolid": raise FormatSTLError('Expected "endsolid %s", got "%s"' % (name, line)) def parse_bin(self): file = open(self.filename, 'rb') import struct try: header = file.read(80) logging.info('Parsing STL binary model') logging.info('HEADER: %s' % header) (count,) = struct.unpack('<I', file.read(4)) logging.info('COUNT: %d' % count) for i in range(count): normal = struct.unpack('<fff', file.read(12)) points = [] for i in range(3): points.append(struct.unpack('<fff', file.read(12))) try: f = Facet(Point3(points[0]), Point3(points[1]), Point3(points[2]) ) f.normal = Vector3(Point3(normal)) f.normal.normalize() self.facets.append(f) except AssertionError: pass attribute_byte_count = file.read(2) except: self.facets = [] raise FormatSTLError def parse(self): f = open(self.filename, 'r') data = f.read() if "facet normal" in data[0:300] and "outer loop" in data[0:300]: self.parse_text() else: self.parse_bin() def get_extremal(self): rand_point = self.facets[0].points[0] extremals = {'minx': rand_point.x, 'maxx': rand_point.x, 'miny': rand_point.y, 'maxy': rand_point.y, 'minz': rand_point.z, 'maxz': rand_point.z} for facet in self.facets: for p in facet: extremals['minx'] = min(extremals['minx'], p.x) extremals['maxx'] = max(extremals['maxx'], p.x) extremals['miny'] = min(extremals['miny'], p.y) extremals['maxy'] = max(extremals['maxy'], p.y) extremals['minz'] = min(extremals['minz'], p.z) extremals['maxz'] = max(extremals['maxz'], p.z) extremals['xsize'] = extremals['maxx'] - extremals['minx'] extremals['ysize'] = extremals['maxy'] - extremals['miny'] extremals['zsize'] = extremals['maxz'] - extremals['minz'] extremals['diameter'] = math.sqrt(extremals['xsize']**2 + extremals['ysize']**2 + extremals['zsize']**2) extremals['xcenter'] = (extremals['maxx'] + extremals['minx']) / 2 extremals['ycenter'] = (extremals['maxy'] + extremals['miny']) / 2 extremals['zcenter'] = (extremals['maxz'] + extremals['minz']) / 2 return extremals def changeDirection(self, direction): #This strange 3 lines make reverse transformation for i in range(3): for f in self.facets: f.changeDirection(self.direction) self.direction = direction for f in self.facets: f.changeDirection(direction) self.update() def zoom_x(self, scale): for f in self.facets: f.zoom_x(scale) self.update() def zoom_y(self, scale): for f in self.facets: f.zoom_y(scale) self.update() def zoom_z(self, scale): for f in self.facets: f.zoom_z(scale) self.update() def add_x(self, v): for f in self.facets: f.add_x(v) def add_y(self, v): for f in self.facets: f.add_y(v) def add_z(self, v): for f in self.facets: f.add_z(v) def zoom(self, scale): for f in self.facets: f.zoom(scale) self.update() def max_size(self): max_v = 0 for v in (self.ex['minx'], self.ex['maxx'], self.ex['miny'], self.ex['maxy'], self.ex['minz'], self.ex['maxz']): max_v = max(max_v, abs(v)) return max_v def centering(self): self.add_x(-self.ex['xcenter']) self.add_y(-self.ex['ycenter']) self.add_z(-self.ex['zcenter']) self.update()
class Slice: def __init__(self, model, z, asrt=True): print 'new slice %.2f' % z self.asrt = asrt self.calculated_fully_scan_old = None self.calculated_fully_scan = None self.calculated_get_loops = None self.calculated_get_shape = None self.stl_model = model self.z = z self.lines = [] self.ex = {'minx': MAXSIZE, 'maxx': -MAXSIZE, 'miny': MAXSIZE, 'maxy': -MAXSIZE} if len(model.facets) > MAXFACETS: logging.error("Cant slice %d facets. The max supposed numbers of facets is %.2f" % (len(model.facets), MAXFACETS)) raise SizeSliceError("Cant slice so big model") for facet in model.intersect_facets(z): if facet.isIntersect(z): line = facet.intersect(z) if line.length > EPS: self.lines.append(line) for line in self.lines: for p in line: self.ex['minx'] = min(self.ex['minx'], p.x) self.ex['maxx'] = max(self.ex['maxx'], p.x) self.ex['miny'] = min(self.ex['miny'], p.y) self.ex['maxy'] = max(self.ex['maxy'], p.y) if self.max_size() > MAXSIZE: logging.error("Cant slice %.2f model. The max size is %.2f" % (self.max_size(), MAXSIZE)) raise SizeSliceError("Cant slice so big model") self.sorted_y = [] self.tree_x = IntervalTree(0) print "lines in slice: %d" % len(self.lines) def max_size(self): x = max(-self.ex['minx'], self.ex['maxx']) y = max(-self.ex['miny'], self.ex['maxy']) return max(x, y) def __len__(self): return len(self.lines) def __nonzero__(self): return True #Used it for find first index, self.sorted_y[idx] > y. #If there are numbers, equal with y, answer may be any index of them. def find_y_old(self, y, asrt=True, left=True): l = 0 r = len(self.sorted_y) while r > l: m = (r + l) // 2 if asrt and equal(self.sorted_y[m][0], y): logging.info('You want find_y(%.3f) with Assert mode, but there are such y' % y) assert 0 if self.sorted_y[m][0] > y: r = m else: l = m + 1 # l == r return l #simple fully scan each STEP row #returns list[Line2] def fully_scan_old(self): if len(self.lines) <= 1: return [] if not self.calculated_fully_scan_old is None: return self.calculated_fully_scan_old i = 0 self.sorted_y = [] for line in self.lines: self.sorted_y.append((line.p1.y, i)) self.sorted_y.append((line.p2.y, -1)) i += 1 self.sorted_y.sort() self.tree_x = IntervalTree(len(self.sorted_y)) for line in self.lines: l = self.find_y_old(line.p1.y, False) r = self.find_y_old(line.p2.y, False) if l > r: (l, r) = (r, l) self.tree_x.push(l, r - 1, line) miny = self.ex['miny'] maxy = self.ex['maxy'] y = miny + CORRECTION ans = [] number_tries = 0 while y < maxy: while self.exist(y): logging.info('Correction in fully_scan_old') y += CORRECTION if number_tries > 3: logging.error("I tired to tries so much! ;(") if self.asrt: raise stl_utils.FormatSTLError('Cant slice') try: ans.extend(self.get_lines_in_row_old(y)) y += STEP number_tries = 0 except AssertionError: y += CORRECTION number_tries += 1 self.calculated_fully_scan = ans return ans #Remeber, it doesnt work if there is edge in the row def get_lines_in_row_old(self, y): ans = [] intersects = [] index = self.find_y_old(y) for line in self.tree_x.get(index): if line.isIntersect(y): intersects.append(line.calcIntersect(y)) else: logging.info('get_lines_in_row: It can not be! ;(') assert 0 if len(intersects) % 2 == 1: logging.error('get_lines_in_row: I have odd number of intersects %f slice %f row. Trying to increment less.' % (self.z, y)) assert 0 else: intersects.sort() for i in range(len(intersects) // 2): p1 = Point2(intersects[2 * i], y) p2 = Point2(intersects[2 * i + 1], y) ans.append(Line2(p1, p2)) return ans #this function is not used now def get_points_in_row(self, y): intersects = [] for line in self.lines: try: if line.isIntersect(y): intersects.append(line.calcIntersect(y)) except AssertionError: intersects.append(line.p1.x) intersects.append(line.p2.x) intersects.sort() ans = [intersects[0]] last = ans[0] for i in intersects[1:]: if abs(i - last) > EPS: ans.append(i) last = i return ans #find first element, >= y def find_y_left(self, y): l = 0 r = len(self.sorted_y) while r > l: m = (r + l) // 2 if self.sorted_y[m][0] + EPS > y: r = m else: l = m + 1 # l == r return l #find first element, > y def find_y_right(self, y): l = 0 r = len(self.sorted_y) while r > l: m = (r + l) // 2 if self.sorted_y[m][0] - EPS > y: r = m else: l = m + 1 # l == r return l def find_y(self, y): l = 0 r = len(self.sorted_y) while r > l: m = (r + l) // 2 if equal(y, self.sorted_y[m][0]): return m if self.sorted_y[m][0] > y: r = m else: l = m + 1 if l == len(self.sorted_y): assert 0 if equal(y, self.sorted_y[l][0]): return l # not found assert 0 def get_loops(self): if not self.calculated_get_loops is None: return self.calculated_get_loops self.sorted_y = [] i = 0 for line in self.lines: self.sorted_y.append((line.p1.y, i)) i += 1 self.sorted_y.sort() ans = [] checked = [] for j in range(len(self.lines)): checked.append(False) for j in range(len(self.lines)): if checked[j]: continue checked[j] = True line = self.lines[j] loop = [line.p1] p = line.p2 missed = 0 while p.dist(line.p1) > EPS: if p.dist(loop[-1]) > 0.5: loop.append(p) else: missed += 1 nearest = False dist = 100 nearest_idx = -1 i = self.find_y_left(p.y - CORRECTION) while (i < len(self.sorted_y)) and ((self.sorted_y[i][0] - CORRECTION) < p.y): if not checked[self.sorted_y[i][1]]: if p.dist(self.lines[self.sorted_y[i][1]].p1) < dist: dist = p.dist(self.lines[self.sorted_y[i][1]].p1) nearest = self.lines[self.sorted_y[i][1]].p2 nearest_idx = self.sorted_y[i][1] i += 1 if dist > CORRECTION: logging.info("Can't find nearest point. Loop is missed.") loop = [] break p = nearest checked[nearest_idx] = True print "point in loop %d" % len(loop) print "missed point in loop %d" % missed print if len(loop) > 2: ans.append(Loop(loop)) self.calculated_get_loops = ans return ans #fing loops first def fully_scan(self): if not self.calculated_fully_scan is None: return self.calculated_fully_scan loops = self.get_loops() lines = [] indx = 0 for loop in loops: prev = loop.points[-1] for p in loop: lines.append((prev, p, indx, loop.is_hole())) prev = p indx += 1 self.sorted_y = [] for (start, end, i, hole) in lines: self.sorted_y.append((start.y, i)) self.sorted_y.sort() self.tree_x = IntervalTree(len(self.sorted_y)) for (start, end, i, hole) in lines: l = self.find_y(start.y) r = self.find_y(end.y) if l > r: (l, r) = (r, l) self.tree_x.push(l, r, (start, end, i, hole)) miny = self.ex['miny'] maxy = self.ex['maxy'] y = miny ans = [] while y < maxy: while self.exist(y): logging.info('Correction in fully_scan') y += CORRECTION ans.extend(self.get_lines_in_row(y)) y += STEP self.calculated_fully_scan = ans return ans def exist(self, y): try: self.find_y(y) return True except AssertionError: return False def get_lines_in_row(self, y): ans = [] intersects = [] index = self.find_y_right(y) max_i = 0 for (start, end, i, hole) in self.tree_x.get(index): if i > max_i: max_i = i line = Line2(start, end) if line.isIntersect(y): intersects.append((line.calcIntersect(y), i, hole)) assert len(intersects) % 2 == 0 active_loop = dict() for i in range(max_i + 1): active_loop[i] = False intersects.sort() last = [] for i in range(len(intersects)): if not active_loop[intersects[i][1]]: active_loop[intersects[i][1]] = True last.append(intersects[i][2]) else: active_loop[intersects[i][1]] = False assert last.pop() == intersects[i][2] if last and not last[-1]: p1 = Point2(intersects[i][0], y) p2 = Point2(intersects[i + 1][0], y) ans.append(Line2(p1, p2)) return ans def get_shape(self): if not self.calculated_get_shape is None: return self.calculated_get_shape loops = self.get_loops() ans = [] for loop in loops: prev = loop.points[-1] for p in loop: ans.append(Line2(prev, p)) prev = p self.calculated_get_shape = ans return ans
class Slice: def __init__(self, model, z): self.stl_model = model self.z = z self.lines = [] self.sorted_y = [] self.ex = {'minx': MAXSIZE, 'maxx': -MAXSIZE, 'miny': MAXSIZE, 'maxy': -MAXSIZE} if self.stl_model.loaded: if model.max_size() > MAXSIZE: logging.error("Cant slice %.2f model. The max size is %.2f" % (model.max_size(), MAXSIZE)) raise SizeSliceError("Cant slice so big model") if len(model.facets) > MAXFACETS: logging.error("Cant slice %d facets. The max supposed numbers of facets is %.2f" % (len(model.facets), MAXFACETS)) raise SizeSliceError("Cant slice so big model") for facet in model.facets: if facet.isIntersect(z): line = facet.intersect(z) if line.length > EPS: self.lines.append(line) for line in self.lines: for p in line: self.ex['minx'] = min(self.ex['minx'], p.x) self.ex['maxx'] = max(self.ex['maxx'], p.x) self.ex['miny'] = min(self.ex['miny'], p.y) self.ex['maxy'] = max(self.ex['maxy'], p.y) for line in self.lines: self.sorted_y.append(line.p1.y) self.sorted_y.append(line.p2.y) self.sorted_y.sort() #making interval tree for fast search intersected lines self.tree_x = IntervalTree(len(self.sorted_y)) for line in self.lines: l = self.find_y(line.p1.y, False) r = self.find_y(line.p2.y, False) if l > r: (l, r) = (r, l) self.tree_x.push(l, r - 1, line) def setHeight(self, height): # Set new height and recalculate list of facets self.z = height self.lines = [] if self.stl_model and self.stl_model.max_size() > MAXSIZE: logging.error("Cant slice %.2f model. The max size is %.2f" % (model.max_size(), MAXSIZE)) raise SizeSliceError("Cant slice so big model") for facet in self.stl_model.facets: if facet.isIntersect(self.z): self.lines.append(facet.intersect(self.z)) def __len__(self): return len(self.lines) #Used it for find first index, self.sorted_y[idx] > y. #If there are numbers, equal with y, answer may be any index of them. def find_y(self, y, asrt=True): l = 0 r = len(self.sorted_y) while r > l: m = (r + l) // 2 if asrt: if equal(self.sorted_y[m], y): logging.info('You want find_y(%.3f) with Assert mode, but there are such y' % y) assert 0 if self.sorted_y[m] > y: r = m else: l = m + 1 # l == r return l #simple fully scan each STEP row #returns list[Line2] def fully_scan(self): if len(self.lines) <= 1: return [] miny = self.ex['miny'] maxy = self.ex['maxy'] y = miny + EPS ans = [] number_tries = 0 while y < maxy: if number_tries > 3: logging.error("I tired to tries so much! ;(") raise stl_utils.FormatSTLError('Cant slice') try: ans.extend(self.get_lines_in_row(y)) y += STEP number_tries = 0 except AssertionError: y += EPS number_tries += 1 return ans #scans only significant rows #returns list[tuple[Point2]] #it wasn't a good idea. no profit def intellectual_scan(self): if len(self.lines) <= 1: return [] all_y = [] for line in self.lines: all_y.append(line.p1.y) all_y.append(line.p2.y) all_y.sort() ans = [] y_prev = all_y[0] for y_next in all_y[1:]: if y_next - STEP / 5 < y_prev: y_prev = y_next continue try: lines_prev = self.get_lines_in_row(y_prev + EPS) lines_next = self.get_lines_in_row(y_next - EPS) except: logging.error("Can't get_lines_in_row. %f slice, %f row" % (self.z, y_next)) raise stl_utils.FormatSTLError("Can't get_lines_in_row. %f slice, %f row" % (self.z, y_next)) #continue if len(lines_prev) != len(lines_next): logging.error("Ooops, the lengths is not equal!") raise stl_utils.FormatSTLError("Ooops, the lengths is not equal! Row %f" % y_next) #continue for i in range(len(lines_prev)): ans.append((lines_prev[i].p1, lines_prev[i].p2, lines_next[i].p2, lines_next[i].p1)) y_prev = y_next return ans #Remeber, it doesnt work if there is edge in the row def get_lines_in_row(self, y): ans = [] intersects = [] index = self.find_y(y) for line in self.tree_x.get(index): if line.isIntersect(y): intersects.append(line.calcIntersect(y)) else: logging.info('get_lines_in_row: It can not be! ;(') assert 0 if len(intersects) % 2 == 1: logging.error('get_lines_in_row: I have odd number of intersects %f slice %f row. Trying to increment less.' % (self.z, y)) assert 0 else: intersects.sort() for i in range(len(intersects) // 2): p1 = Point2(intersects[2 * i], y) p2 = Point2(intersects[2 * i + 1], y) ans.append(Line2(p1, p2)) return ans #this function is not used now def get_points_in_row(self, y): intersects = [] for line in self.lines: try: if line.isIntersect(y): intersects.append(line.calcIntersect(y)) except AssertionError: intersects.append(line.p1.x) intersects.append(line.p2.x) intersects.sort() ans = [intersects[0]] last = ans[0] for i in intersects[1:]: if abs(i - last) > EPS: ans.append(i) last = i return ans def make_correct_loops(self): print len(self.lines) return [] '''