def _update_caches(self): if self._use_kdtree: self._t_kdtree = TriangleKdtree(self.triangles()) self.__uuid = str(uuid.uuid4()) self.__flat_groups_cache = {} # the kdtree is up-to-date again self._dirty = False
class Model(BaseModel): def __init__(self, use_kdtree=True): import pycam.Exporters.STLExporter super(Model, self).__init__() self._triangles = [] self._item_groups.append(self._triangles) self._export_function = pycam.Exporters.STLExporter.STLExporter # marker for state of kdtree and uuid self._dirty = True # enable/disable kdtree self._use_kdtree = use_kdtree self._t_kdtree = None self.__uuid = None def __len__(self): """ Return the number of available items in the model. This is mainly useful for evaluating an empty model as False. """ return len(self._triangles) def copy(self): result = self.__class__(use_kdtree=self._use_kdtree) for triangle in self.triangles(): result.append(triangle.copy()) return result @property def uuid(self): if (self.__uuid is None) or self._dirty: self._update_caches() return self.__uuid def append(self, item): super(Model, self).append(item) if isinstance(item, Triangle): self._triangles.append(item) # we assume, that the kdtree needs to be rebuilt again self._dirty = True def reset_cache(self): super(Model, self).reset_cache() # the triangle kdtree needs to be reset after transforming the model self._update_caches() def _update_caches(self): if self._use_kdtree: self._t_kdtree = TriangleKdtree(self.triangles()) self.__uuid = str(uuid.uuid4()) # the kdtree is up-to-date again self._dirty = False def triangles(self, minx=-INFINITE, miny=-INFINITE, minz=-INFINITE, maxx=+INFINITE, maxy=+INFINITE, maxz=+INFINITE): if (minx == miny == minz == -INFINITE) and (maxx == maxy == maxz == +INFINITE): return self._triangles if self._use_kdtree: # update the kdtree, if new triangles were added meanwhile if self._dirty: self._update_caches() return self._t_kdtree.Search(minx, maxx, miny, maxy) return self._triangles def get_waterline_contour(self, plane, callback=None): collision_lines = [] progress_max = 2 * len(self._triangles) counter = 0 for t in self._triangles: if callback and callback(percent=100.0 * counter / progress_max): return collision_line = plane.intersect_triangle(t, counter_clockwise=True) if collision_line is not None: collision_lines.append(collision_line) else: counter += 1 counter += 1 # combine these lines into polygons contour = ContourModel(plane=plane) for line in collision_lines: if callback and callback(percent=100.0 * counter / progress_max): return contour.append(line) counter += 1 log.debug("Waterline: %f - %d - %s", plane.p[2], len(contour.get_polygons()), [len(p.get_lines()) for p in contour.get_polygons()]) return contour
class Model(BaseModel): def __init__(self, use_kdtree=True): super(Model, self).__init__() self._triangles = [] self._item_groups.append(self._triangles) self._export_function = pycam.Exporters.STLExporter.STLExporter # marker for state of kdtree and uuid self._dirty = True # enable/disable kdtree self._use_kdtree = use_kdtree self._t_kdtree = None self.__flat_groups_cache = {} self.__uuid = None def __len__(self): """ Return the number of available items in the model. This is mainly useful for evaluating an empty model as False. """ return len(self._triangles) @property def uuid(self): if (self.__uuid is None) or self._dirty: self._update_caches() return self.__uuid def append(self, item): super(Model, self).append(item) if isinstance(item, Triangle): self._triangles.append(item) # we assume, that the kdtree needs to be rebuilt again self._dirty = True def reset_cache(self): super(Model, self).reset_cache() # the triangle kdtree needs to be reset after transforming the model self._update_caches() def _update_caches(self): if self._use_kdtree: self._t_kdtree = TriangleKdtree(self.triangles()) self.__uuid = str(uuid.uuid4()) self.__flat_groups_cache = {} # the kdtree is up-to-date again self._dirty = False def triangles(self, minx=-INFINITE, miny=-INFINITE, minz=-INFINITE, maxx=+INFINITE, maxy=+INFINITE, maxz=+INFINITE): if (minx == miny == minz == -INFINITE) \ and (maxx == maxy == maxz == +INFINITE): return self._triangles if self._use_kdtree: # update the kdtree, if new triangles were added meanwhile if self._dirty: self._update_caches() return self._t_kdtree.Search(minx, maxx, miny, maxy) return self._triangles def get_waterline_contour(self, plane): collision_lines = [] for t in self._triangles: collision_line = plane.intersect_triangle(t, counter_clockwise=True) if not collision_line is None: collision_lines.append(collision_line) # combine these lines into polygons contour = ContourModel(plane=plane) for line in collision_lines: contour.append(line) log.debug("Waterline: %f - %d - %s" % (plane.p.z, len(contour.get_polygons()), [len(p.get_lines()) for p in contour.get_polygons()])) return contour def get_flat_areas(self, min_area=None): """ Find plane areas (combinations of triangles) bigger than 'min_area' and ignore vertical planes. The result is cached. """ if not self.__flat_groups_cache.has_key(min_area): def has_shared_edge(t1, t2): count = 0 for p in (t1.p1, t1.p2, t1.p3): if p in (t2.p1, t2.p2, t2.p3): count += 1 return count >= 2 groups = [] for t in self.triangles(): # Find all groups with the same direction (see 'normal') that # share at least one edge with the current triangle. touch_groups = [] if t.normal.z == 0: # ignore vertical triangles continue for group_index, group in enumerate(groups): if t.normal == group[0].normal: for group_t in group: if has_shared_edge(t, group_t): touch_groups.append(group_index) break if len(touch_groups) > 1: # combine multiple areas with this new triangle touch_groups.reverse() combined = [t] for touch_group_index in touch_groups: combined.extend(groups.pop(touch_group_index)) groups.append(combined) elif len(touch_groups) == 1: groups[touch_groups[0]].append(t) else: groups.append([t]) # check the size of each area if not min_area is None: groups = [ group for group in groups if sum([t.get_area() for t in group]) >= min_area ] self.__flat_groups_cache[min_area] = groups return self.__flat_groups_cache[min_area]
class Model(BaseModel): def __init__(self, use_kdtree=True): import pycam.Exporters.STLExporter super().__init__() self._triangles = [] self._item_groups.append(self._triangles) self._export_function = pycam.Exporters.STLExporter.STLExporter # marker for state of kdtree and uuid self._dirty = True # enable/disable kdtree self._use_kdtree = use_kdtree self._t_kdtree = None self.__uuid = None def __len__(self): """ Return the number of available items in the model. This is mainly useful for evaluating an empty model as False. """ return len(self._triangles) def __iter__(self): yield from self._triangles def copy(self): result = self.__class__(use_kdtree=self._use_kdtree) for triangle in self.triangles(): result.append(triangle.copy()) return result @property def uuid(self): if (self.__uuid is None) or self._dirty: self._update_caches() return self.__uuid def append(self, item): super().append(item) if isinstance(item, Triangle): self._triangles.append(item) # we assume, that the kdtree needs to be rebuilt again self._dirty = True def reset_cache(self): super().reset_cache() # the triangle kdtree needs to be reset after transforming the model self._update_caches() def _update_caches(self): if self._use_kdtree: self._t_kdtree = TriangleKdtree(self.triangles()) self.__uuid = str(uuid.uuid4()) # the kdtree is up-to-date again self._dirty = False def triangles(self, minx=-INFINITE, miny=-INFINITE, minz=-INFINITE, maxx=+INFINITE, maxy=+INFINITE, maxz=+INFINITE): if (minx == miny == minz == -INFINITE) and (maxx == maxy == maxz == +INFINITE): return self._triangles if self._use_kdtree: # update the kdtree, if new triangles were added meanwhile if self._dirty: self._update_caches() return self._t_kdtree.search(minx, maxx, miny, maxy) return self._triangles def get_waterline_contour(self, plane, callback=None): collision_lines = [] progress_max = 2 * len(self._triangles) counter = 0 for t in self._triangles: if callback and callback(percent=100.0 * counter / progress_max): return collision_line = plane.intersect_triangle(t, counter_clockwise=True) if collision_line is not None: collision_lines.append(collision_line) else: counter += 1 counter += 1 # combine these lines into polygons contour = ContourModel(plane=plane) for line in collision_lines: if callback and callback(percent=100.0 * counter / progress_max): return contour.append(line) counter += 1 log.debug("Waterline: %f - %d - %s", plane.p[2], len(contour.get_polygons()), [len(p.get_lines()) for p in contour.get_polygons()]) return contour def to_x3d(self, color): yield "<Shape>" yield "<Appearance>" yield ('<Material diffuseColor="{:f} {:f} {:f}" transparency="{:f}" />' .format(color["red"], color["green"], color["blue"], 1 - color["alpha"])) yield "</Appearance>" yield "<TriangleSet>" yield '<Coordinate point="' for triangle in self: p1, p2, p3 = triangle.get_points() # use the proper direction in order to let normals point outwards yield " ".join("{:f} {:f} {:f}".format(*point) for point in (p1, p3, p2)) yield '"/>' yield "</TriangleSet>" yield "</Shape>"