def run(self): if not self._node: return ## If the scene node is a group, use the hull of the children to calculate its hull. if self._node.callDecoration("isGroup"): hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32)) for child in self._node.getChildren(): child_hull = child.callDecoration("getConvexHull") if child_hull: hull.setPoints(numpy.append(hull.getPoints(), child_hull.getPoints(), axis = 0)) if hull.getPoints().size < 3: self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return Job.yieldThread() else: if not self._node.getMeshData(): return mesh = self._node.getMeshData() vertex_data = mesh.getTransformed(self._node.getWorldTransformation()).getVertices() # Don't use data below 0. TODO; We need a better check for this as this gives poor results for meshes with long edges. vertex_data = vertex_data[vertex_data[:,1]>0] hull = Polygon(numpy.rint(vertex_data[:, [0, 2]]).astype(int)) # First, calculate the normal convex hull around the points hull = hull.getConvexHull() # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull. # This is done because of rounding errors. hull = hull.getMinkowskiHull(Polygon(numpy.array([[-1, -1], [-1, 1], [1, 1], [1, -1]], numpy.float32))) profile = Application.getInstance().getMachineManager().getActiveProfile() if profile: if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): # Printing one at a time and it's not an object in a group self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull)) head_hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_with_fans_polygon"),numpy.float32))) self._node.callDecoration("setConvexHullHead", head_hull) hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_polygon"),numpy.float32))) else: self._node.callDecoration("setConvexHullHead", None) hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot()) self._node.callDecoration("setConvexHullNode", hull_node) self._node.callDecoration("setConvexHull", hull) self._node.callDecoration("setConvexHullJob", None) if self._node.getParent().callDecoration("isGroup"): job = self._node.getParent().callDecoration("getConvexHullJob") if job: job.cancel() self._node.getParent().callDecoration("setConvexHull", None) hull_node = self._node.getParent().callDecoration("getConvexHullNode") if hull_node: hull_node.setParent(None)
def _computeDisallowedAreasPrinted(self, used_extruders): result = {} for extruder in used_extruders: result[extruder.getId()] = [] #Currently, the only normally printed object is the prime tower. if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable") == True: prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value") machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") if not self._global_container_stack.getProperty("machine_center_is_zero", "value"): prime_tower_x = prime_tower_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left. prime_tower_y = prime_tower_y + machine_depth / 2 prime_tower_area = Polygon([ [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], [prime_tower_x - prime_tower_size, prime_tower_y], ]) prime_tower_area = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(0)) for extruder in used_extruders: result[extruder.getId()].append(prime_tower_area) #The prime tower location is the same for each extruder, regardless of offset. return result
def _updateDisallowedAreas(self): if not self._active_container_stack: return disallowed_areas = self._active_container_stack.getProperty("machine_disallowed_areas", "value") areas = [] skirt_size = self._getSkirtSize(self._active_container_stack) if disallowed_areas: # Extend every area already in the disallowed_areas with the skirt size. for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) poly = poly.getMinkowskiHull(Polygon(numpy.array([ [-skirt_size, 0], [-skirt_size * 0.707, skirt_size * 0.707], [0, skirt_size], [skirt_size * 0.707, skirt_size * 0.707], [skirt_size, 0], [skirt_size * 0.707, -skirt_size * 0.707], [0, -skirt_size], [-skirt_size * 0.707, -skirt_size * 0.707] ], numpy.float32))) areas.append(poly) # Add the skirt areas around the borders of the build plate. if skirt_size > 0: half_machine_width = self._active_container_stack.getProperty("machine_width", "value") / 2 half_machine_depth = self._active_container_stack.getProperty("machine_depth", "value") / 2 areas.append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + skirt_size, half_machine_depth - skirt_size], [-half_machine_width + skirt_size, -half_machine_depth + skirt_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - skirt_size, -half_machine_depth + skirt_size], [half_machine_width - skirt_size, half_machine_depth - skirt_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - skirt_size, half_machine_depth - skirt_size], [-half_machine_width + skirt_size, half_machine_depth - skirt_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + skirt_size, -half_machine_depth + skirt_size], [half_machine_width - skirt_size, -half_machine_depth + skirt_size] ], numpy.float32))) self._disallowed_areas = areas
def _offsetHull(self, convex_hull: Polygon) -> Polygon: """Offset the convex hull with settings that influence the collision area. :param convex_hull: Polygon of the original convex hull. :return: New Polygon instance that is offset with everything that influences the collision area. """ horizontal_expansion = max( self._getSettingProperty("xy_offset", "value"), self._getSettingProperty("xy_offset_layer_0", "value")) mold_width = 0 if self._getSettingProperty("mold_enabled", "value"): mold_width = self._getSettingProperty("mold_width", "value") hull_offset = horizontal_expansion + mold_width if hull_offset > 0: #TODO: Implement Minkowski subtraction for if the offset < 0. expansion_polygon = Polygon( numpy.array( [[-hull_offset, -hull_offset], [-hull_offset, hull_offset], [hull_offset, hull_offset], [hull_offset, -hull_offset]], numpy.float32)) return convex_hull.getMinkowskiHull(expansion_polygon) else: return convex_hull
def _add2DAdhesionMargin(self, poly: Polygon) -> Polygon: if not self._global_stack: return Polygon() # Compensate for raft/skirt/brim # Add extra margin depending on adhesion type adhesion_type = self._global_stack.getProperty("adhesion_type", "value") if adhesion_type == "raft": extra_margin = max( 0, self._getSettingProperty("raft_margin", "value")) elif adhesion_type == "brim": extra_margin = max( 0, self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")) elif adhesion_type == "none": extra_margin = 0 elif adhesion_type == "skirt": extra_margin = max( 0, self._getSettingProperty("skirt_gap", "value") + self._getSettingProperty("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")) else: raise Exception( "Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?" ) # Adjust head_and_fans with extra margin if extra_margin > 0: extra_margin_polygon = Polygon.approximatedCircle(extra_margin) poly = poly.getMinkowskiHull(extra_margin_polygon) return poly
def _add2DAdhesionMargin(self, poly: Polygon) -> Polygon: if not self._global_stack: return Polygon() # Compensate for raft/skirt/brim # Add extra margin depending on adhesion type adhesion_type = self._global_stack.getProperty("adhesion_type", "value") max_length_available = 0.5 * min( self._getSettingProperty("machine_width", "value"), self._getSettingProperty("machine_depth", "value") ) if adhesion_type == "raft": extra_margin = min(max_length_available, max(0, self._getSettingProperty("raft_margin", "value"))) elif adhesion_type == "brim": extra_margin = min(max_length_available, max(0, self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))) elif adhesion_type == "none": extra_margin = 0 elif adhesion_type == "skirt": extra_margin = min(max_length_available, max( 0, self._getSettingProperty("skirt_gap", "value") + self._getSettingProperty("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))) else: raise Exception("Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?") # Adjust head_and_fans with extra margin if extra_margin > 0: extra_margin_polygon = Polygon.approximatedCircle(extra_margin) poly = poly.getMinkowskiHull(extra_margin_polygon) return poly
def _updateDisallowedAreas(self): if not self._active_instance or not self._active_profile: return disallowed_areas = self._active_instance.getMachineSettingValue("machine_disallowed_areas") areas = [] skirt_size = 0.0 if self._active_profile: skirt_size = self._getSkirtSize(self._active_profile) if disallowed_areas: for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) poly = poly.getMinkowskiHull(Polygon(numpy.array([ [-skirt_size, 0], [-skirt_size * 0.707, skirt_size * 0.707], [0, skirt_size], [skirt_size * 0.707, skirt_size * 0.707], [skirt_size, 0], [skirt_size * 0.707, -skirt_size * 0.707], [0, -skirt_size], [-skirt_size * 0.707, -skirt_size * 0.707] ], numpy.float32))) areas.append(poly) if skirt_size > 0: half_machine_width = self._active_instance.getMachineSettingValue("machine_width") / 2 half_machine_depth = self._active_instance.getMachineSettingValue("machine_depth") / 2 areas.append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + skirt_size, half_machine_depth - skirt_size], [-half_machine_width + skirt_size, -half_machine_depth + skirt_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - skirt_size, -half_machine_depth + skirt_size], [half_machine_width - skirt_size, half_machine_depth - skirt_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - skirt_size, half_machine_depth - skirt_size], [-half_machine_width + skirt_size, half_machine_depth - skirt_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + skirt_size, -half_machine_depth + skirt_size], [half_machine_width - skirt_size, -half_machine_depth + skirt_size] ], numpy.float32))) self._disallowed_areas = areas
def fromNode(cls, node, min_offset, scale=1): transform = node._transformation transform_x = transform._data[0][3] transform_y = transform._data[2][3] arrange_align = Preferences.getInstance().getValue( "mesh/arrange_align") if arrange_align: bb = node.getBoundingBox() bb_points = numpy.array( [[bb.right, bb.back], [bb.left, bb.back], [bb.left, bb.front], [bb.right, bb.front]], dtype=numpy.float32) polygon = Polygon(bb_points) else: hull_verts = node.callDecoration("getConvexHull") # If a model is too small then it will not contain any points if hull_verts is None or not hull_verts.getPoints().any(): return None, None # For one_at_a_time printing you need the convex hull head. polygon = node.callDecoration("getConvexHullHead") or hull_verts offset_verts = polygon.getMinkowskiHull( Polygon.approximatedCircle(min_offset)) offset_points = copy.deepcopy(offset_verts._points) # x, y offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x) offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y) offset_shape_arr = ShapeArray.fromPolygon(offset_points, scale=scale) hull_points = copy.deepcopy(polygon._points) hull_points[:, 0] = numpy.add(hull_points[:, 0], -transform_x) hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y) hull_shape_arr = ShapeArray.fromPolygon(hull_points, scale=scale) return offset_shape_arr, hull_shape_arr
def run(self): if not self._node: return ## If the scene node is a group, use the hull of the children to calculate its hull. if self._node.callDecoration("isGroup"): hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32)) for child in self._node.getChildren(): child_hull = child.callDecoration("getConvexHull") if child_hull: hull.setPoints(numpy.append(hull.getPoints(), child_hull.getPoints(), axis = 0)) if hull.getPoints().size < 3: self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return else: if not self._node.getMeshData(): return mesh = self._node.getMeshData() vertex_data = mesh.getTransformed(self._node.getWorldTransformation()).getVertices() hull = Polygon(numpy.rint(vertex_data[:, [0, 2]]).astype(int)) # First, calculate the normal convex hull around the points hull = hull.getConvexHull() #print("hull: " , self._node.callDecoration("isGroup"), " " , hull.getPoints()) # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull. hull = hull.getMinkowskiHull(Polygon(numpy.array([[-1, -1], [-1, 1], [1, 1], [1, -1]], numpy.float32))) hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot()) self._node.callDecoration("setConvexHullNode", hull_node) self._node.callDecoration("setConvexHull", hull) self._node.callDecoration("setConvexHullJob", None)
def fromNode( cls, node: "SceneNode", min_offset: float, scale: float = 0.5, include_children: bool = False ) -> Tuple[Optional["ShapeArray"], Optional["ShapeArray"]]: """Instantiate an offset and hull ShapeArray from a scene node. :param node: source node where the convex hull must be present :param min_offset: offset for the offset ShapeArray :param scale: scale the coordinates :return: A tuple containing an offset and hull shape array """ transform = node._transformation transform_x = transform._data[0][3] transform_y = transform._data[2][3] hull_verts = node.callDecoration("getConvexHull") # If a model is too small then it will not contain any points if hull_verts is None or not hull_verts.getPoints().any(): return None, None # For one_at_a_time printing you need the convex hull head. hull_head_verts = node.callDecoration( "getConvexHullHead") or hull_verts if hull_head_verts is None: hull_head_verts = Polygon() # If the child-nodes are included, adjust convex hulls as well: if include_children: children = node.getAllChildren() if not children is None: for child in children: # 'Inefficient' combination of convex hulls through known code rather than mess it up: child_hull = child.callDecoration("getConvexHull") if not child_hull is None: hull_verts = hull_verts.unionConvexHulls(child_hull) child_hull_head = child.callDecoration( "getConvexHullHead") or child_hull if not child_hull_head is None: hull_head_verts = hull_head_verts.unionConvexHulls( child_hull_head) offset_verts = hull_head_verts.getMinkowskiHull( Polygon.approximatedCircle(min_offset)) offset_points = copy.deepcopy(offset_verts._points) # x, y offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x) offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y) offset_shape_arr = ShapeArray.fromPolygon(offset_points, scale=scale) hull_points = copy.deepcopy(hull_verts._points) hull_points[:, 0] = numpy.add(hull_points[:, 0], -transform_x) hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y) hull_shape_arr = ShapeArray.fromPolygon(hull_points, scale=scale) # x, y return offset_shape_arr, hull_shape_arr
def test_parts_of_fromNode(): from UM.Math.Polygon import Polygon p = Polygon(numpy.array([[-2, -2], [2, -2], [2, 2], [-2, 2]], dtype=numpy.int32)) offset = 1 p_offset = p.getMinkowskiHull(Polygon.approximatedCircle(offset)) assert len(numpy.where(p_offset._points[:, 0] >= 2.9)) > 0 assert len(numpy.where(p_offset._points[:, 0] <= -2.9)) > 0 assert len(numpy.where(p_offset._points[:, 1] >= 2.9)) > 0 assert len(numpy.where(p_offset._points[:, 1] <= -2.9)) > 0
def test_parts_of_fromNode(): from UM.Math.Polygon import Polygon p = Polygon( numpy.array([[-2, -2], [2, -2], [2, 2], [-2, 2]], dtype=numpy.int32)) offset = 1 p_offset = p.getMinkowskiHull(Polygon.approximatedCircle(offset)) assert len(numpy.where(p_offset._points[:, 0] >= 2.9)) > 0 assert len(numpy.where(p_offset._points[:, 0] <= -2.9)) > 0 assert len(numpy.where(p_offset._points[:, 1] >= 2.9)) > 0 assert len(numpy.where(p_offset._points[:, 1] <= -2.9)) > 0
def test_parts_of_fromNode2(): from UM.Math.Polygon import Polygon p = Polygon(numpy.array([[-2, -2], [2, -2], [2, 2], [-2, 2]], dtype=numpy.int32) * 2) # 4x4 offset = 13.3 scale = 0.5 p_offset = p.getMinkowskiHull(Polygon.approximatedCircle(offset)) shape_arr1 = ShapeArray.fromPolygon(p._points, scale = scale) shape_arr2 = ShapeArray.fromPolygon(p_offset._points, scale = scale) assert shape_arr1.arr.shape[0] >= (4 * scale) - 1 # -1 is to account for rounding errors assert shape_arr2.arr.shape[0] >= (2 * offset + 4) * scale - 1
def test_parts_of_fromNode(): """Just adding some stuff to ensure fromNode works as expected. Some parts should actually be in UM""" from UM.Math.Polygon import Polygon p = Polygon( numpy.array([[-2, -2], [2, -2], [2, 2], [-2, 2]], dtype=numpy.int32)) offset = 1 p_offset = p.getMinkowskiHull(Polygon.approximatedCircle(offset)) assert len(numpy.where(p_offset._points[:, 0] >= 2.9)) > 0 assert len(numpy.where(p_offset._points[:, 0] <= -2.9)) > 0 assert len(numpy.where(p_offset._points[:, 1] >= 2.9)) > 0 assert len(numpy.where(p_offset._points[:, 1] <= -2.9)) > 0
def run(self): if not self._node or not self._node.getMeshData(): return mesh = self._node.getMeshData() vertexData = mesh.getTransformed(self._node.getWorldTransformation()).getVertices() hull = Polygon(numpy.rint(vertexData[:, [0, 2]]).astype(int)) # First, calculate the normal convex hull around the points hull = hull.getConvexHull() # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull. hull = hull.getMinkowskiHull(Polygon(numpy.array([[-1, -1], [-1, 1], [1, 1], [1, -1]], numpy.float32))) hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot()) self._node._convex_hull = hull delattr(self._node, "_convex_hull_job")
def _offsetHull(self, convex_hull: Polygon) -> Polygon: horizontal_expansion = max( self._getSettingProperty("xy_offset", "value"), self._getSettingProperty("xy_offset_layer_0", "value")) mold_width = 0 if self._getSettingProperty("mold_enabled", "value"): mold_width = self._getSettingProperty("mold_width", "value") hull_offset = horizontal_expansion + mold_width if hull_offset > 0: #TODO: Implement Minkowski subtraction for if the offset < 0. expansion_polygon = Polygon( numpy.array( [[-hull_offset, -hull_offset], [-hull_offset, hull_offset], [hull_offset, hull_offset], [hull_offset, -hull_offset]], numpy.float32)) return convex_hull.getMinkowskiHull(expansion_polygon) else: return convex_hull
def _offsetHull(self, convex_hull: Polygon) -> Polygon: horizontal_expansion = max( self._getSettingProperty("xy_offset", "value"), self._getSettingProperty("xy_offset_layer_0", "value") ) mold_width = 0 if self._getSettingProperty("mold_enabled", "value"): mold_width = self._getSettingProperty("mold_width", "value") hull_offset = horizontal_expansion + mold_width if hull_offset > 0: #TODO: Implement Minkowski subtraction for if the offset < 0. expansion_polygon = Polygon(numpy.array([ [-hull_offset, -hull_offset], [-hull_offset, hull_offset], [hull_offset, hull_offset], [hull_offset, -hull_offset] ], numpy.float32)) return convex_hull.getMinkowskiHull(expansion_polygon) else: return convex_hull
def fromNode(cls, node, min_offset, scale = 0.5, include_children = False): transform = node._transformation transform_x = transform._data[0][3] transform_y = transform._data[2][3] hull_verts = node.callDecoration("getConvexHull") # If a model is too small then it will not contain any points if hull_verts is None or not hull_verts.getPoints().any(): return None, None # For one_at_a_time printing you need the convex hull head. hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts if hull_head_verts is None: hull_head_verts = Polygon() # If the child-nodes are included, adjust convex hulls as well: if include_children: children = node.getAllChildren() if not children is None: for child in children: # 'Inefficient' combination of convex hulls through known code rather than mess it up: child_hull = child.callDecoration("getConvexHull") if not child_hull is None: hull_verts = hull_verts.unionConvexHulls(child_hull) child_hull_head = child.callDecoration("getConvexHullHead") or child_hull if not child_hull_head is None: hull_head_verts = hull_head_verts.unionConvexHulls(child_hull_head) offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset)) offset_points = copy.deepcopy(offset_verts._points) # x, y offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x) offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y) offset_shape_arr = ShapeArray.fromPolygon(offset_points, scale = scale) hull_points = copy.deepcopy(hull_verts._points) hull_points[:, 0] = numpy.add(hull_points[:, 0], -transform_x) hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y) hull_shape_arr = ShapeArray.fromPolygon(hull_points, scale = scale) # x, y return offset_shape_arr, hull_shape_arr
def _computeDisallowedAreasStatic(self, border_size, used_extruders): #Convert disallowed areas to polygons and dilate them. machine_disallowed_polygons = [] for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"): polygon = Polygon(numpy.array(area, numpy.float32)) polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size)) machine_disallowed_polygons.append(polygon) result = {} for extruder in used_extruders: extruder_id = extruder.getId() offset_x = extruder.getProperty("machine_nozzle_offset_x", "value") if offset_x is None: offset_x = 0 offset_y = extruder.getProperty("machine_nozzle_offset_y", "value") if offset_y is None: offset_y = 0 result[extruder_id] = [] for polygon in machine_disallowed_polygons: result[extruder_id].append(polygon.translate(offset_x, offset_y)) #Compensate for the nozzle offset of this extruder. #Add the border around the edge of the build volume. left_unreachable_border = 0 right_unreachable_border = 0 top_unreachable_border = 0 bottom_unreachable_border = 0 #The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders. for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks(): other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value") other_offset_y = other_extruder.getProperty("machine_nozzle_offset_y", "value") left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x) right_unreachable_border = max(right_unreachable_border, other_offset_x - offset_x) top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y) bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y) half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2 if self._shape != "elliptic": if border_size - left_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border], [-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border] ], numpy.float32))) if border_size + right_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border], [half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border] ], numpy.float32))) if border_size + bottom_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border], [-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border] ], numpy.float32))) if border_size - top_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border], [half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border] ], numpy.float32))) else: sections = 32 arc_vertex = [0, half_machine_depth - border_size] for i in range(0, sections): quadrant = math.floor(4 * i / sections) vertices = [] if quadrant == 0: vertices.append([-half_machine_width, half_machine_depth]) elif quadrant == 1: vertices.append([-half_machine_width, -half_machine_depth]) elif quadrant == 2: vertices.append([half_machine_width, -half_machine_depth]) elif quadrant == 3: vertices.append([half_machine_width, half_machine_depth]) vertices.append(arc_vertex) angle = 2 * math.pi * (i + 1) / sections arc_vertex = [-(half_machine_width - border_size) * math.sin(angle), (half_machine_depth - border_size) * math.cos(angle)] vertices.append(arc_vertex) result[extruder_id].append(Polygon(numpy.array(vertices, numpy.float32))) if border_size > 0: result[extruder_id].append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + border_size, 0] ], numpy.float32))) result[extruder_id].append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [ half_machine_width, half_machine_depth], [ 0, half_machine_depth - border_size] ], numpy.float32))) result[extruder_id].append(Polygon(numpy.array([ [ half_machine_width, half_machine_depth], [ half_machine_width, -half_machine_depth], [ half_machine_width - border_size, 0] ], numpy.float32))) result[extruder_id].append(Polygon(numpy.array([ [ half_machine_width,-half_machine_depth], [-half_machine_width,-half_machine_depth], [ 0, -half_machine_depth + border_size] ], numpy.float32))) return result
def _updateDisallowedAreas(self): if not self._global_container_stack: return self._error_areas = [] extruder_manager = ExtruderManager.getInstance() used_extruders = extruder_manager.getUsedExtruderStacks() disallowed_border_size = self._getEdgeDisallowedSize() if not used_extruders: # If no extruder is used, assume that the active extruder is used (else nothing is drawn) if extruder_manager.getActiveExtruderStack(): used_extruders = [extruder_manager.getActiveExtruderStack()] else: used_extruders = [self._global_container_stack] result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added. prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders) prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking. #Check if prime positions intersect with disallowed areas. for extruder in used_extruders: extruder_id = extruder.getId() collision = False for prime_polygon in prime_areas[extruder_id]: for disallowed_polygon in prime_disallowed_areas[extruder_id]: if prime_polygon.intersectsPolygon(disallowed_polygon) is not None: collision = True break if collision: break #Also check other prime positions (without additional offset). for other_extruder_id in prime_areas: if extruder_id == other_extruder_id: #It is allowed to collide with itself. continue for other_prime_polygon in prime_areas[other_extruder_id]: if prime_polygon.intersectsPolygon(other_prime_polygon): collision = True break if collision: break if collision: break result_areas[extruder_id].extend(prime_areas[extruder_id]) nozzle_disallowed_areas = extruder.getProperty("nozzle_disallowed_areas", "value") for area in nozzle_disallowed_areas: polygon = Polygon(numpy.array(area, numpy.float32)) polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size)) result_areas[extruder_id].append(polygon) #Don't perform the offset on these. # Add prime tower location as disallowed area. prime_tower_collision = False prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders) for extruder_id in prime_tower_areas: for prime_tower_area in prime_tower_areas[extruder_id]: for area in result_areas[extruder_id]: if prime_tower_area.intersectsPolygon(area) is not None: prime_tower_collision = True break if prime_tower_collision: #Already found a collision. break if not prime_tower_collision: result_areas[extruder_id].extend(prime_tower_areas[extruder_id]) else: self._error_areas.extend(prime_tower_areas[extruder_id]) self._has_errors = len(self._error_areas) > 0 self._disallowed_areas = [] for extruder_id in result_areas: self._disallowed_areas.extend(result_areas[extruder_id])
def run(self): if not self._node: return ## If the scene node is a group, use the hull of the children to calculate its hull. if self._node.callDecoration("isGroup"): hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32)) for child in self._node.getChildren(): child_hull = child.callDecoration("getConvexHull") if child_hull: hull.setPoints( numpy.append(hull.getPoints(), child_hull.getPoints(), axis=0)) if hull.getPoints().size < 3: self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return Job.yieldThread() else: if not self._node.getMeshData(): return mesh = self._node.getMeshData() vertex_data = mesh.getTransformed( self._node.getWorldTransformation()).getVertices() # Don't use data below 0. # TODO; We need a better check for this as this gives poor results for meshes with long edges. vertex_data = vertex_data[vertex_data[:, 1] >= 0] # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices # This is done to greatly speed up further convex hull calculations as the convex hull # becomes much less complex when dealing with highly detailed models. vertex_data = numpy.round(vertex_data, 1) vertex_data = vertex_data[:, [ 0, 2 ]] # Drop the Y components to project to 2D. # Grab the set of unique points. # # This basically finds the unique rows in the array by treating them as opaque groups of bytes # which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch. # See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array vertex_byte_view = numpy.ascontiguousarray(vertex_data).view( numpy.dtype( (numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1]))) _, idx = numpy.unique(vertex_byte_view, return_index=True) vertex_data = vertex_data[idx] # Select the unique rows by index. hull = Polygon(vertex_data) # First, calculate the normal convex hull around the points hull = hull.getConvexHull() # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull. # This is done because of rounding errors. hull = hull.getMinkowskiHull( Polygon( numpy.array( [[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32))) global_stack = Application.getInstance().getGlobalContainerStack() if global_stack: if global_stack.getProperty( "print_sequence", "value") == "one_at_a_time" and not self._node.getParent( ).callDecoration("isGroup"): # Printing one at a time and it's not an object in a group self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull)) head_and_fans = Polygon( numpy.array( global_stack.getProperty( "machine_head_with_fans_polygon", "value"), numpy.float32)) # Full head hull is used to actually check the order. full_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHeadFull", full_head_hull) mirrored = copy.deepcopy(head_and_fans) mirrored.mirror([0, 0], [0, 1]) #Mirror horizontally. mirrored.mirror([0, 0], [1, 0]) #Mirror vertically. head_and_fans = head_and_fans.intersectionConvexHulls(mirrored) # Min head hull is used for the push free min_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHead", min_head_hull) hull = hull.getMinkowskiHull( Polygon( numpy.array( global_stack.getProperty("machine_head_polygon", "value"), numpy.float32))) else: self._node.callDecoration("setConvexHullHead", None) if self._node.getParent( ) is None: # Node was already deleted before job is done. self._node.callDecoration("setConvexHullNode", None) self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return hull_node = ConvexHullNode.ConvexHullNode( self._node, hull, Application.getInstance().getController().getScene().getRoot()) self._node.callDecoration("setConvexHullNode", hull_node) self._node.callDecoration("setConvexHull", hull) self._node.callDecoration("setConvexHullJob", None) if self._node.getParent() and self._node.getParent().callDecoration( "isGroup"): job = self._node.getParent().callDecoration("getConvexHullJob") if job: job.cancel() self._node.getParent().callDecoration("setConvexHull", None) hull_node = self._node.getParent().callDecoration( "getConvexHullNode") if hull_node: hull_node.setParent(None)
class BuildVolume(SceneNode): VolumeOutlineColor = Color(12, 169, 227, 255) raftThicknessChanged = Signal() def __init__(self, parent = None): super().__init__(parent) self._width = 0 self._height = 0 self._depth = 0 self._shader = None self._grid_mesh = None self._grid_shader = None self._disallowed_areas = [] self._disallowed_area_mesh = None self._prime_tower_area = None self._prime_tower_area_mesh = None self.setCalculateBoundingBox(False) self._volume_aabb = None self._raft_thickness = 0.0 self._adhesion_type = None self._platform = Platform(self) self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) self._onGlobalContainerStackChanged() self._active_extruder_stack = None ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) self._onActiveExtruderStackChanged() self._has_errors = False def setWidth(self, width): if width: self._width = width def setHeight(self, height): if height: self._height = height def setDepth(self, depth): if depth: self._depth = depth def getDisallowedAreas(self): return self._disallowed_areas def setDisallowedAreas(self, areas): self._disallowed_areas = areas def render(self, renderer): if not self.getMeshData(): return True if not self._shader: self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader")) self._grid_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "grid.shader")) renderer.queueNode(self, mode = RenderBatch.RenderMode.Lines) renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True) if self._disallowed_area_mesh: renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9) if self._prime_tower_area_mesh: renderer.queueNode(self, mesh = self._prime_tower_area_mesh, shader = self._shader, transparent=True, backface_cull=True, sort=-8) return True ## Recalculates the build volume & disallowed areas. def rebuild(self): if not self._width or not self._height or not self._depth: return min_w = -self._width / 2 max_w = self._width / 2 min_h = 0.0 max_h = self._height min_d = -self._depth / 2 max_d = self._depth / 2 mb = MeshBuilder() # Outline 'cube' of the build volume mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor) self.setMeshData(mb.build()) mb = MeshBuilder() mb.addQuad( Vector(min_w, min_h - 0.2, min_d), Vector(max_w, min_h - 0.2, min_d), Vector(max_w, min_h - 0.2, max_d), Vector(min_w, min_h - 0.2, max_d) ) for n in range(0, 6): v = mb.getVertex(n) mb.setVertexUVCoordinates(n, v[0], v[2]) self._grid_mesh = mb.build() disallowed_area_height = 0.1 disallowed_area_size = 0 if self._disallowed_areas: mb = MeshBuilder() color = Color(0.0, 0.0, 0.0, 0.15) for polygon in self._disallowed_areas: points = polygon.getPoints() first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) for point in points: new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d)) mb.addFace(first, previous_point, new_point, color = color) previous_point = new_point # Find the largest disallowed area to exclude it from the maximum scale bounds. # This is a very nasty hack. This pretty much only works for UM machines. # This disallowed area_size needs a -lot- of rework at some point in the future: TODO if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area. size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1])) else: size = 0 disallowed_area_size = max(size, disallowed_area_size) self._disallowed_area_mesh = mb.build() else: self._disallowed_area_mesh = None if self._prime_tower_area: mb = MeshBuilder() color = Color(1.0, 0.0, 0.0, 0.5) points = self._prime_tower_area.getPoints() first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) for point in points: new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d)) mb.addFace(first, previous_point, new_point, color=color) previous_point = new_point self._prime_tower_area_mesh = mb.build() else: self._prime_tower_area_mesh = None self._volume_aabb = AxisAlignedBox( minimum = Vector(min_w, min_h - 1.0, min_d), maximum = Vector(max_w, max_h - self._raft_thickness, max_d)) bed_adhesion_size = 0.0 container_stack = Application.getInstance().getGlobalContainerStack() if container_stack: bed_adhesion_size = self._getBedAdhesionSize(container_stack) # As this works better for UM machines, we only add the disallowed_area_size for the z direction. # This is probably wrong in all other cases. TODO! # The +1 and -1 is added as there is always a bit of extra room required to work properly. scale_to_max_bounds = AxisAlignedBox( minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + disallowed_area_size - bed_adhesion_size + 1), maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness, max_d - disallowed_area_size + bed_adhesion_size - 1) ) Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds def getBoundingBox(self): return self._volume_aabb def _buildVolumeMessage(self): Message(catalog.i18nc( "@info:status", "The build volume height has been reduced due to the value of the" " \"Print Sequence\" setting to prevent the gantry from colliding" " with printed models.")).show() def getRaftThickness(self): return self._raft_thickness def _updateRaftThickness(self): old_raft_thickness = self._raft_thickness self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value") self._raft_thickness = 0.0 if self._adhesion_type == "raft": self._raft_thickness = ( self._global_container_stack.getProperty("raft_base_thickness", "value") + self._global_container_stack.getProperty("raft_interface_thickness", "value") + self._global_container_stack.getProperty("raft_surface_layers", "value") * self._global_container_stack.getProperty("raft_surface_thickness", "value") + self._global_container_stack.getProperty("raft_airgap", "value")) # Rounding errors do not matter, we check if raft_thickness has changed at all if old_raft_thickness != self._raft_thickness: self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World) self.raftThicknessChanged.emit() def _onGlobalContainerStackChanged(self): if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._global_container_stack = Application.getInstance().getGlobalContainerStack() if self._global_container_stack: self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged) self._width = self._global_container_stack.getProperty("machine_width", "value") machine_height = self._global_container_stack.getProperty("machine_height", "value") if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time": self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height) if self._height < machine_height: self._buildVolumeMessage() else: self._height = self._global_container_stack.getProperty("machine_height", "value") self._depth = self._global_container_stack.getProperty("machine_depth", "value") self._updateDisallowedAreas() self._updateRaftThickness() self.rebuild() def _onActiveExtruderStackChanged(self): if self._active_extruder_stack: self._active_extruder_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._active_extruder_stack = ExtruderManager.getInstance().getActiveExtruderStack() if self._active_extruder_stack: self._active_extruder_stack.propertyChanged.connect(self._onSettingPropertyChanged) def _onSettingPropertyChanged(self, setting_key, property_name): if property_name != "value": return rebuild_me = False if setting_key == "print_sequence": machine_height = self._global_container_stack.getProperty("machine_height", "value") if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time": self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height) if self._height < machine_height: self._buildVolumeMessage() else: self._height = self._global_container_stack.getProperty("machine_height", "value") rebuild_me = True if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence": self._updateDisallowedAreas() rebuild_me = True if setting_key in self._raft_settings: self._updateRaftThickness() rebuild_me = True if rebuild_me: self.rebuild() def hasErrors(self): return self._has_errors def _updateDisallowedAreas(self): if not self._global_container_stack: return self._has_errors = False # Reset. disallowed_areas = copy.deepcopy( self._global_container_stack.getProperty("machine_disallowed_areas", "value")) areas = [] machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") self._prime_tower_area = None # Add prime tower location as disallowed area. if self._global_container_stack.getProperty("prime_tower_enable", "value") == True: prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value") prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") - machine_width / 2 prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") + machine_depth / 2 self._prime_tower_area = Polygon([ [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], [prime_tower_x - prime_tower_size, prime_tower_y], ]) # Add extruder prime locations as disallowed areas. # Probably needs some rework after coordinate system change. extruder_manager = ExtruderManager.getInstance() extruders = extruder_manager.getMachineExtruders(self._global_container_stack.getId()) for single_extruder in extruders: extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value") extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value") # TODO: calculate everything in CuraEngine/Firmware/lower left as origin coordinates. # Here we transform the extruder prime pos (lower left as origin) to Cura coordinates # (center as origin, y from back to front) prime_x = extruder_prime_pos_x - machine_width / 2 prime_y = machine_depth / 2 - extruder_prime_pos_y disallowed_areas.append([ [prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], [prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], ]) bed_adhesion_size = self._getBedAdhesionSize(self._global_container_stack) if disallowed_areas: # Extend every area already in the disallowed_areas with the skirt size. for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) areas.append(poly) if self._prime_tower_area: self._prime_tower_area = self._prime_tower_area.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) # Add the skirt areas around the borders of the build plate. if bed_adhesion_size > 0: half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2 areas.append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) # Check if the prime tower area intersects with any of the other areas. # If this is the case, keep the polygon seperate, so it can be drawn in red. # If not, add it back to disallowed area's, so it's rendered as normal. collision = False if self._prime_tower_area: for area in areas: if self._prime_tower_area.intersectsPolygon(area) is not None: collision = True break if not collision: areas.append(self._prime_tower_area) self._prime_tower_area = None self._has_errors = collision self._disallowed_areas = areas ## Convenience function to calculate the size of the bed adhesion in directions x, y. def _getBedAdhesionSize(self, container_stack): skirt_size = 0.0 # If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects if container_stack.getProperty("print_sequence", "value") == "one_at_a_time": return 0.1 # Return a very small value, so we do draw disallowed area's near the edges. adhesion_type = container_stack.getProperty("adhesion_type", "value") if adhesion_type == "skirt": skirt_distance = container_stack.getProperty("skirt_gap", "value") skirt_line_count = container_stack.getProperty("skirt_line_count", "value") skirt_size = skirt_distance + (skirt_line_count * container_stack.getProperty("skirt_brim_line_width", "value")) elif adhesion_type == "brim": skirt_size = container_stack.getProperty("brim_line_count", "value") * container_stack.getProperty("skirt_brim_line_width", "value") elif adhesion_type == "raft": skirt_size = container_stack.getProperty("raft_margin", "value") if container_stack.getProperty("draft_shield_enabled", "value"): skirt_size += container_stack.getProperty("draft_shield_dist", "value") if container_stack.getProperty("xy_offset", "value"): skirt_size += container_stack.getProperty("xy_offset", "value") return skirt_size def _clamp(self, value, min_value, max_value): return max(min(value, max_value), min_value) _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"] _raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"] _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"] _tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
def _updateDisallowedAreas(self): if not self._global_container_stack: return self._has_errors = False # Reset. disallowed_areas = copy.deepcopy( self._global_container_stack.getProperty("machine_disallowed_areas", "value")) areas = [] machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") self._prime_tower_area = None # Add prime tower location as disallowed area. if self._global_container_stack.getProperty("prime_tower_enable", "value") == True: prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value") prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") - machine_width / 2 prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") + machine_depth / 2 self._prime_tower_area = Polygon([ [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], [prime_tower_x - prime_tower_size, prime_tower_y], ]) # Add extruder prime locations as disallowed areas. # Probably needs some rework after coordinate system change. extruder_manager = ExtruderManager.getInstance() extruders = extruder_manager.getMachineExtruders(self._global_container_stack.getId()) for single_extruder in extruders: extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value") extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value") # TODO: calculate everything in CuraEngine/Firmware/lower left as origin coordinates. # Here we transform the extruder prime pos (lower left as origin) to Cura coordinates # (center as origin, y from back to front) prime_x = extruder_prime_pos_x - machine_width / 2 prime_y = machine_depth / 2 - extruder_prime_pos_y disallowed_areas.append([ [prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], [prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], ]) bed_adhesion_size = self._getBedAdhesionSize(self._global_container_stack) if disallowed_areas: # Extend every area already in the disallowed_areas with the skirt size. for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) areas.append(poly) if self._prime_tower_area: self._prime_tower_area = self._prime_tower_area.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) # Add the skirt areas around the borders of the build plate. if bed_adhesion_size > 0: half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2 areas.append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) # Check if the prime tower area intersects with any of the other areas. # If this is the case, keep the polygon seperate, so it can be drawn in red. # If not, add it back to disallowed area's, so it's rendered as normal. collision = False if self._prime_tower_area: for area in areas: if self._prime_tower_area.intersectsPolygon(area) is not None: collision = True break if not collision: areas.append(self._prime_tower_area) self._prime_tower_area = None self._has_errors = collision self._disallowed_areas = areas
def _updateDisallowedAreas(self): if not self._global_container_stack: return self._has_errors = False # Reset. self._error_areas = [] disallowed_areas = copy.deepcopy( self._global_container_stack.getProperty( "machine_disallowed_areas", "value")) areas = [] machine_width = self._global_container_stack.getProperty( "machine_width", "value") machine_depth = self._global_container_stack.getProperty( "machine_depth", "value") prime_tower_area = None # Add prime tower location as disallowed area. if ExtruderManager.getInstance().getResolveOrValue( "prime_tower_enable") == True: prime_tower_size = self._global_container_stack.getProperty( "prime_tower_size", "value") prime_tower_x = self._global_container_stack.getProperty( "prime_tower_position_x", "value") - machine_width / 2 prime_tower_y = -self._global_container_stack.getProperty( "prime_tower_position_y", "value") + machine_depth / 2 prime_tower_area = Polygon([ [ prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size ], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], [prime_tower_x - prime_tower_size, prime_tower_y], ]) disallowed_polygons = [] # Check if prime positions intersect with disallowed areas prime_collision = False if disallowed_areas: for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) # Minkowski with zero, to ensure that the polygon is correct & watertight. poly = poly.getMinkowskiHull(Polygon.approximatedCircle(0)) disallowed_polygons.append(poly) extruder_manager = ExtruderManager.getInstance() extruders = extruder_manager.getMachineExtruders( self._global_container_stack.getId()) prime_polygons = [] # Each extruder has it's own prime location for extruder in extruders: prime_x = extruder.getProperty("extruder_prime_pos_x", "value") - machine_width / 2 prime_y = machine_depth / 2 - extruder.getProperty( "extruder_prime_pos_y", "value") prime_polygon = Polygon([ [prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], [prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], ]) prime_polygon = prime_polygon.getMinkowskiHull( Polygon.approximatedCircle(0)) collision = False # Check if prime polygon is intersecting with any of the other disallowed areas. # Note that we check the prime area without bed adhesion. for poly in disallowed_polygons: if prime_polygon.intersectsPolygon(poly) is not None: collision = True break # Also collide with other prime positions for poly in prime_polygons: if prime_polygon.intersectsPolygon(poly) is not None: collision = True break if not collision: # Prime area is valid. Add as normal. # Once it's added like this, it will recieve a bed adhesion offset, just like the others. prime_polygons.append(prime_polygon) else: self._error_areas.append(prime_polygon) prime_collision = collision or prime_collision disallowed_polygons.extend(prime_polygons) disallowed_border_size = self._getEdgeDisallowedSize() # Extend every area already in the disallowed_areas with the skirt size. if disallowed_areas: for poly in disallowed_polygons: poly = poly.getMinkowskiHull( Polygon.approximatedCircle(disallowed_border_size)) areas.append(poly) # Add the skirt areas around the borders of the build plate. if disallowed_border_size > 0: half_machine_width = self._global_container_stack.getProperty( "machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty( "machine_depth", "value") / 2 areas.append( Polygon( numpy.array( [[-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [ -half_machine_width + disallowed_border_size, half_machine_depth - disallowed_border_size ], [ -half_machine_width + disallowed_border_size, -half_machine_depth + disallowed_border_size ]], numpy.float32))) areas.append( Polygon( numpy.array( [[half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [ half_machine_width - disallowed_border_size, -half_machine_depth + disallowed_border_size ], [ half_machine_width - disallowed_border_size, half_machine_depth - disallowed_border_size ]], numpy.float32))) areas.append( Polygon( numpy.array( [[-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [ half_machine_width - disallowed_border_size, half_machine_depth - disallowed_border_size ], [ -half_machine_width + disallowed_border_size, half_machine_depth - disallowed_border_size ]], numpy.float32))) areas.append( Polygon( numpy.array( [[half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [ -half_machine_width + disallowed_border_size, -half_machine_depth + disallowed_border_size ], [ half_machine_width - disallowed_border_size, -half_machine_depth + disallowed_border_size ]], numpy.float32))) # Check if the prime tower area intersects with any of the other areas. # If this is the case, add it to the error area's so it can be drawn in red. # If not, add it back to disallowed area's, so it's rendered as normal. prime_tower_collision = False if prime_tower_area: # Using Minkowski of 0 fixes the prime tower area so it's rendered correctly prime_tower_area = prime_tower_area.getMinkowskiHull( Polygon.approximatedCircle(0)) for area in areas: if prime_tower_area.intersectsPolygon(area) is not None: prime_tower_collision = True break if not prime_tower_collision: areas.append(prime_tower_area) else: self._error_areas.append(prime_tower_area) # The buildplate has errors if either prime tower or prime has a colission. self._has_errors = prime_tower_collision or prime_collision self._disallowed_areas = areas
def _computeDisallowedAreasStatic(self, border_size, used_extruders): #Convert disallowed areas to polygons and dilate them. machine_disallowed_polygons = [] for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"): polygon = Polygon(numpy.array(area, numpy.float32)) polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size)) machine_disallowed_polygons.append(polygon) result = {} for extruder in used_extruders: extruder_id = extruder.getId() offset_x = extruder.getProperty("machine_nozzle_offset_x", "value") if offset_x is None: offset_x = 0 offset_y = extruder.getProperty("machine_nozzle_offset_y", "value") if offset_y is None: offset_y = 0 result[extruder_id] = [] for polygon in machine_disallowed_polygons: result[extruder_id].append(polygon.translate(offset_x, offset_y)) #Compensate for the nozzle offset of this extruder. #Add the border around the edge of the build volume. left_unreachable_border = 0 right_unreachable_border = 0 top_unreachable_border = 0 bottom_unreachable_border = 0 #The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders. for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks(): other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value") other_offset_y = other_extruder.getProperty("machine_nozzle_offset_y", "value") left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x) right_unreachable_border = max(right_unreachable_border, other_offset_x - offset_x) top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y) bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y) half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2 if border_size - left_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border], [-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border] ], numpy.float32))) if border_size + right_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border], [half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border] ], numpy.float32))) if border_size + bottom_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border], [-half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border] ], numpy.float32))) if border_size - top_unreachable_border > 0: result[extruder_id].append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border], [half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border] ], numpy.float32))) return result
def run(self): if not self._node: return ## If the scene node is a group, use the hull of the children to calculate its hull. if self._node.callDecoration("isGroup"): hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32)) for child in self._node.getChildren(): child_hull = child.callDecoration("getConvexHull") if child_hull: hull.setPoints(numpy.append(hull.getPoints(), child_hull.getPoints(), axis = 0)) if hull.getPoints().size < 3: self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return Job.yieldThread() else: if not self._node.getMeshData(): return mesh = self._node.getMeshData() vertex_data = mesh.getTransformed(self._node.getWorldTransformation()).getVertices() # Don't use data below 0. # TODO; We need a better check for this as this gives poor results for meshes with long edges. vertex_data = vertex_data[vertex_data[:,1] >= 0] # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices # This is done to greatly speed up further convex hull calculations as the convex hull # becomes much less complex when dealing with highly detailed models. vertex_data = numpy.round(vertex_data, 1) duplicates = (vertex_data[:,0] == vertex_data[:,1]) | (vertex_data[:,1] == vertex_data[:,2]) | (vertex_data[:,0] == vertex_data[:,2]) vertex_data = numpy.delete(vertex_data, numpy.where(duplicates), axis = 0) hull = Polygon(vertex_data[:, [0, 2]]) # First, calculate the normal convex hull around the points hull = hull.getConvexHull() # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull. # This is done because of rounding errors. hull = hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32))) profile = Application.getInstance().getMachineManager().getWorkingProfile() if profile: if profile.getSettingValue("print_sequence") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): # Printing one at a time and it's not an object in a group self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull)) head_and_fans = Polygon(numpy.array(profile.getSettingValue("machine_head_with_fans_polygon"), numpy.float32)) # Full head hull is used to actually check the order. full_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHeadFull", full_head_hull) mirrored = copy.deepcopy(head_and_fans) mirrored.mirror([0, 0], [0, 1]) #Mirror horizontally. mirrored.mirror([0, 0], [1, 0]) #Mirror vertically. head_and_fans = head_and_fans.intersectionConvexHulls(mirrored) # Min head hull is used for the push free min_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHead", min_head_hull) hull = hull.getMinkowskiHull(Polygon(numpy.array(profile.getSettingValue("machine_head_polygon"),numpy.float32))) else: self._node.callDecoration("setConvexHullHead", None) if self._node.getParent() is None: # Node was already deleted before job is done. self._node.callDecoration("setConvexHullNode",None) self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot()) self._node.callDecoration("setConvexHullNode", hull_node) self._node.callDecoration("setConvexHull", hull) self._node.callDecoration("setConvexHullJob", None) if self._node.getParent() and self._node.getParent().callDecoration("isGroup"): job = self._node.getParent().callDecoration("getConvexHullJob") if job: job.cancel() self._node.getParent().callDecoration("setConvexHull", None) hull_node = self._node.getParent().callDecoration("getConvexHullNode") if hull_node: hull_node.setParent(None)
def run(self): if not self._node: return ## If the scene node is a group, use the hull of the children to calculate its hull. if self._node.callDecoration("isGroup"): hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32)) for child in self._node.getChildren(): child_hull = child.callDecoration("getConvexHull") if child_hull: hull.setPoints( numpy.append(hull.getPoints(), child_hull.getPoints(), axis=0)) if hull.getPoints().size < 3: self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return Job.yieldThread() else: if not self._node.getMeshData(): return mesh = self._node.getMeshData() vertex_data = mesh.getTransformed( self._node.getWorldTransformation()).getVertices() # Don't use data below 0. # TODO; We need a better check for this as this gives poor results for meshes with long edges. vertex_data = vertex_data[vertex_data[:, 1] >= 0] # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices # This is done to greatly speed up further convex hull calculations as the convex hull # becomes much less complex when dealing with highly detailed models. vertex_data = numpy.round(vertex_data, 1) duplicates = (vertex_data[:, 0] == vertex_data[:, 1]) | ( vertex_data[:, 1] == vertex_data[:, 2]) | ( vertex_data[:, 0] == vertex_data[:, 2]) vertex_data = numpy.delete(vertex_data, numpy.where(duplicates), axis=0) hull = Polygon(vertex_data[:, [0, 2]]) # First, calculate the normal convex hull around the points hull = hull.getConvexHull() # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull. # This is done because of rounding errors. hull = hull.getMinkowskiHull( Polygon( numpy.array( [[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32))) profile = Application.getInstance().getMachineManager( ).getWorkingProfile() if profile: if profile.getSettingValue( "print_sequence" ) == "one_at_a_time" and not self._node.getParent().callDecoration( "isGroup"): # Printing one at a time and it's not an object in a group self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull)) head_and_fans = Polygon( numpy.array( profile.getSettingValue( "machine_head_with_fans_polygon"), numpy.float32)) # Full head hull is used to actually check the order. full_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHeadFull", full_head_hull) mirrored = copy.deepcopy(head_and_fans) mirrored.mirror([0, 0], [0, 1]) #Mirror horizontally. mirrored.mirror([0, 0], [1, 0]) #Mirror vertically. head_and_fans = head_and_fans.intersectionConvexHulls(mirrored) # Min head hull is used for the push free min_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHead", min_head_hull) hull = hull.getMinkowskiHull( Polygon( numpy.array( profile.getSettingValue("machine_head_polygon"), numpy.float32))) else: self._node.callDecoration("setConvexHullHead", None) if self._node.getParent( ) is None: # Node was already deleted before job is done. self._node.callDecoration("setConvexHullNode", None) self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return hull_node = ConvexHullNode.ConvexHullNode( self._node, hull, Application.getInstance().getController().getScene().getRoot()) self._node.callDecoration("setConvexHullNode", hull_node) self._node.callDecoration("setConvexHull", hull) self._node.callDecoration("setConvexHullJob", None) if self._node.getParent() and self._node.getParent().callDecoration( "isGroup"): job = self._node.getParent().callDecoration("getConvexHullJob") if job: job.cancel() self._node.getParent().callDecoration("setConvexHull", None) hull_node = self._node.getParent().callDecoration( "getConvexHullNode") if hull_node: hull_node.setParent(None)
def _updateDisallowedAreas(self): if not self._active_container_stack: return disallowed_areas = copy.deepcopy( self._active_container_stack.getProperty("machine_disallowed_areas", "value")) areas = [] # Add extruder prime locations as disallowed areas. # Probably needs some rework after coordinate system change. extruder_manager = ExtruderManager.getInstance() extruders = extruder_manager.getMachineExtruders(self._active_container_stack.getId()) machine_width = self._active_container_stack.getProperty("machine_width", "value") machine_depth = self._active_container_stack.getProperty("machine_depth", "value") for single_extruder in extruders: extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value") extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value") # TODO: calculate everything in CuraEngine/Firmware/lower left as origin coordinates. # Here we transform the extruder prime pos (lower left as origin) to Cura coordinates # (center as origin, y from back to front) prime_x = extruder_prime_pos_x - machine_width / 2 prime_y = machine_depth / 2 - extruder_prime_pos_y disallowed_areas.append([ [prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], [prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], ]) bed_adhesion_size = self._getBedAdhesionSize(self._active_container_stack) if disallowed_areas: # Extend every area already in the disallowed_areas with the skirt size. for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) areas.append(poly) # Add the skirt areas around the borders of the build plate. if bed_adhesion_size > 0: half_machine_width = self._active_container_stack.getProperty("machine_width", "value") / 2 half_machine_depth = self._active_container_stack.getProperty("machine_depth", "value") / 2 areas.append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) self._disallowed_areas = areas
def _computeDisallowedAreasStatic(self, border_size, used_extruders): #Convert disallowed areas to polygons and dilate them. machine_disallowed_polygons = [] for area in self._global_container_stack.getProperty( "machine_disallowed_areas", "value"): polygon = Polygon(numpy.array(area, numpy.float32)) polygon = polygon.getMinkowskiHull( Polygon.approximatedCircle(border_size)) machine_disallowed_polygons.append(polygon) result = {} for extruder in used_extruders: extruder_id = extruder.getId() offset_x = extruder.getProperty("machine_nozzle_offset_x", "value") if not offset_x: offset_x = 0 offset_y = extruder.getProperty("machine_nozzle_offset_y", "value") if not offset_y: offset_y = 0 result[extruder_id] = [] for polygon in machine_disallowed_polygons: result[extruder_id].append( polygon.translate(offset_x, offset_y) ) #Compensate for the nozzle offset of this extruder. #Add the border around the edge of the build volume. left_unreachable_border = 0 right_unreachable_border = 0 top_unreachable_border = 0 bottom_unreachable_border = 0 #The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders. for other_extruder in ExtruderManager.getInstance( ).getActiveExtruderStacks(): other_offset_x = other_extruder.getProperty( "machine_nozzle_offset_x", "value") other_offset_y = other_extruder.getProperty( "machine_nozzle_offset_y", "value") left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x) right_unreachable_border = max(right_unreachable_border, other_offset_x - offset_x) top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y) bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y) half_machine_width = self._global_container_stack.getProperty( "machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty( "machine_depth", "value") / 2 if border_size - left_unreachable_border > 0: result[extruder_id].append( Polygon( numpy.array( [[-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [ -half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border ], [ -half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border ]], numpy.float32))) if border_size + right_unreachable_border > 0: result[extruder_id].append( Polygon( numpy.array( [[half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [ half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border ], [ half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border ]], numpy.float32))) if border_size + bottom_unreachable_border > 0: result[extruder_id].append( Polygon( numpy.array( [[-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [ half_machine_width - border_size - right_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border ], [ -half_machine_width + border_size - left_unreachable_border, half_machine_depth - border_size - bottom_unreachable_border ]], numpy.float32))) if border_size - top_unreachable_border > 0: result[extruder_id].append( Polygon( numpy.array( [[half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [ -half_machine_width + border_size - left_unreachable_border, -half_machine_depth + border_size - top_unreachable_border ], [ half_machine_width - border_size - right_unreachable_border, -half_machine_depth + border_size - top_unreachable_border ]], numpy.float32))) return result
def _updateDisallowedAreas(self): if not self._global_container_stack: return self._error_areas = [] extruder_manager = ExtruderManager.getInstance() used_extruders = extruder_manager.getUsedExtruderStacks() disallowed_border_size = self._getEdgeDisallowedSize() result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added. prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders) prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking. #Check if prime positions intersect with disallowed areas. for extruder in used_extruders: extruder_id = extruder.getId() collision = False for prime_polygon in prime_areas[extruder_id]: for disallowed_polygon in prime_disallowed_areas[extruder_id]: if prime_polygon.intersectsPolygon(disallowed_polygon) is not None: collision = True break if collision: break #Also check other prime positions (without additional offset). for other_extruder_id in prime_areas: if extruder_id == other_extruder_id: #It is allowed to collide with itself. continue for other_prime_polygon in prime_areas[other_extruder_id]: if prime_polygon.intersectsPolygon(other_prime_polygon): collision = True break if collision: break if collision: break result_areas[extruder_id].extend(prime_areas[extruder_id]) nozzle_disallowed_areas = extruder.getProperty("nozzle_disallowed_areas", "value") for area in nozzle_disallowed_areas: polygon = Polygon(numpy.array(area, numpy.float32)) polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size)) result_areas[extruder_id].append(polygon) #Don't perform the offset on these. # Add prime tower location as disallowed area. prime_tower_collision = False prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders) for extruder_id in prime_tower_areas: for prime_tower_area in prime_tower_areas[extruder_id]: for area in result_areas[extruder_id]: if prime_tower_area.intersectsPolygon(area) is not None: prime_tower_collision = True break if prime_tower_collision: #Already found a collision. break if not prime_tower_collision: result_areas[extruder_id].extend(prime_tower_areas[extruder_id]) else: self._error_areas.extend(prime_tower_areas[extruder_id]) self._has_errors = len(self._error_areas) > 0 self._disallowed_areas = [] for extruder_id in result_areas: self._disallowed_areas.extend(result_areas[extruder_id])
def run(self): if not self._node: return ## If the scene node is a group, use the hull of the children to calculate its hull. if self._node.callDecoration("isGroup"): hull = Polygon(numpy.zeros((0, 2), dtype=numpy.int32)) for child in self._node.getChildren(): child_hull = child.callDecoration("getConvexHull") if child_hull: hull.setPoints(numpy.append(hull.getPoints(), child_hull.getPoints(), axis = 0)) if hull.getPoints().size < 3: self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return Job.yieldThread() else: if not self._node.getMeshData(): return mesh = self._node.getMeshData() vertex_data = mesh.getTransformed(self._node.getWorldTransformation()).getVertices() # Don't use data below 0. # TODO; We need a better check for this as this gives poor results for meshes with long edges. vertex_data = vertex_data[vertex_data[:,1] >= 0] # Round the vertex data to 1/10th of a mm, then remove all duplicate vertices # This is done to greatly speed up further convex hull calculations as the convex hull # becomes much less complex when dealing with highly detailed models. vertex_data = numpy.round(vertex_data, 1) vertex_data = vertex_data[:, [0, 2]] # Drop the Y components to project to 2D. # Grab the set of unique points. # # This basically finds the unique rows in the array by treating them as opaque groups of bytes # which are as long as the 2 float64s in each row, and giving this view to numpy.unique() to munch. # See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1]))) _, idx = numpy.unique(vertex_byte_view, return_index=True) vertex_data = vertex_data[idx] # Select the unique rows by index. hull = Polygon(vertex_data) # First, calculate the normal convex hull around the points hull = hull.getConvexHull() # Then, do a Minkowski hull with a simple 1x1 quad to outset and round the normal convex hull. # This is done because of rounding errors. hull = hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32))) global_stack = Application.getInstance().getGlobalContainerStack() if global_stack: if global_stack.getProperty("print_sequence", "value")== "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): # Printing one at a time and it's not an object in a group self._node.callDecoration("setConvexHullBoundary", copy.deepcopy(hull)) head_and_fans = Polygon(numpy.array(global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32)) # Full head hull is used to actually check the order. full_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHeadFull", full_head_hull) mirrored = copy.deepcopy(head_and_fans) mirrored.mirror([0, 0], [0, 1]) #Mirror horizontally. mirrored.mirror([0, 0], [1, 0]) #Mirror vertically. head_and_fans = head_and_fans.intersectionConvexHulls(mirrored) # Min head hull is used for the push free min_head_hull = hull.getMinkowskiHull(head_and_fans) self._node.callDecoration("setConvexHullHead", min_head_hull) hull = hull.getMinkowskiHull(Polygon(numpy.array(global_stack.getProperty("machine_head_polygon","value"),numpy.float32))) else: self._node.callDecoration("setConvexHullHead", None) if self._node.getParent() is None: # Node was already deleted before job is done. self._node.callDecoration("setConvexHullNode",None) self._node.callDecoration("setConvexHull", None) self._node.callDecoration("setConvexHullJob", None) return hull_node = ConvexHullNode.ConvexHullNode(self._node, hull, Application.getInstance().getController().getScene().getRoot()) self._node.callDecoration("setConvexHullNode", hull_node) self._node.callDecoration("setConvexHull", hull) self._node.callDecoration("setConvexHullJob", None) if self._node.getParent() and self._node.getParent().callDecoration("isGroup"): job = self._node.getParent().callDecoration("getConvexHullJob") if job: job.cancel() self._node.getParent().callDecoration("setConvexHull", None) hull_node = self._node.getParent().callDecoration("getConvexHullNode") if hull_node: hull_node.setParent(None)
class BuildVolume(SceneNode): VolumeOutlineColor = Color(12, 169, 227, 255) raftThicknessChanged = Signal() def __init__(self, parent = None): super().__init__(parent) self._width = 0 self._height = 0 self._depth = 0 self._shader = None self._grid_mesh = None self._grid_shader = None self._disallowed_areas = [] self._disallowed_area_mesh = None self._prime_tower_area = None self._prime_tower_area_mesh = None self.setCalculateBoundingBox(False) self._volume_aabb = None self._raft_thickness = 0.0 self._adhesion_type = None self._platform = Platform(self) self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) self._onGlobalContainerStackChanged() self._active_extruder_stack = None ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) self._onActiveExtruderStackChanged() self._has_errors = False def setWidth(self, width): if width: self._width = width def setHeight(self, height): if height: self._height = height def setDepth(self, depth): if depth: self._depth = depth def getDisallowedAreas(self): return self._disallowed_areas def setDisallowedAreas(self, areas): self._disallowed_areas = areas def render(self, renderer): if not self.getMeshData(): return True if not self._shader: self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader")) self._grid_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "grid.shader")) renderer.queueNode(self, mode = RenderBatch.RenderMode.Lines) renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True) if self._disallowed_area_mesh: renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9) if self._prime_tower_area_mesh: renderer.queueNode(self, mesh = self._prime_tower_area_mesh, shader = self._shader, transparent=True, backface_cull=True, sort=-8) return True ## Recalculates the build volume & disallowed areas. def rebuild(self): if not self._width or not self._height or not self._depth: return min_w = -self._width / 2 max_w = self._width / 2 min_h = 0.0 max_h = self._height min_d = -self._depth / 2 max_d = self._depth / 2 mb = MeshBuilder() # Outline 'cube' of the build volume mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, max_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, max_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, max_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, min_h, max_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, min_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, min_h, min_d), Vector(max_w, min_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, max_h, min_d), Vector(min_w, max_h, max_d), color = self.VolumeOutlineColor) mb.addLine(Vector(max_w, max_h, min_d), Vector(max_w, max_h, max_d), color = self.VolumeOutlineColor) self.setMeshData(mb.build()) mb = MeshBuilder() mb.addQuad( Vector(min_w, min_h - 0.2, min_d), Vector(max_w, min_h - 0.2, min_d), Vector(max_w, min_h - 0.2, max_d), Vector(min_w, min_h - 0.2, max_d) ) for n in range(0, 6): v = mb.getVertex(n) mb.setVertexUVCoordinates(n, v[0], v[2]) self._grid_mesh = mb.build() disallowed_area_height = 0.1 disallowed_area_size = 0 if self._disallowed_areas: mb = MeshBuilder() color = Color(0.0, 0.0, 0.0, 0.15) for polygon in self._disallowed_areas: points = polygon.getPoints() first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) for point in points: new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d)) mb.addFace(first, previous_point, new_point, color = color) previous_point = new_point # Find the largest disallowed area to exclude it from the maximum scale bounds. # This is a very nasty hack. This pretty much only works for UM machines. # This disallowed area_size needs a -lot- of rework at some point in the future: TODO if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area. size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1])) else: size = 0 disallowed_area_size = max(size, disallowed_area_size) self._disallowed_area_mesh = mb.build() else: self._disallowed_area_mesh = None if self._prime_tower_area: mb = MeshBuilder() color = Color(1.0, 0.0, 0.0, 0.5) points = self._prime_tower_area.getPoints() first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d)) for point in points: new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d)) mb.addFace(first, previous_point, new_point, color=color) previous_point = new_point self._prime_tower_area_mesh = mb.build() else: self._prime_tower_area_mesh = None self._volume_aabb = AxisAlignedBox( minimum = Vector(min_w, min_h - 1.0, min_d), maximum = Vector(max_w, max_h - self._raft_thickness, max_d)) bed_adhesion_size = 0.0 container_stack = Application.getInstance().getGlobalContainerStack() if container_stack: bed_adhesion_size = self._getBedAdhesionSize(container_stack) # As this works better for UM machines, we only add the disallowed_area_size for the z direction. # This is probably wrong in all other cases. TODO! # The +1 and -1 is added as there is always a bit of extra room required to work properly. scale_to_max_bounds = AxisAlignedBox( minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + disallowed_area_size - bed_adhesion_size + 1), maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness, max_d - disallowed_area_size + bed_adhesion_size - 1) ) Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds def getBoundingBox(self): return self._volume_aabb def _buildVolumeMessage(self): Message(catalog.i18nc( "@info:status", "The build volume height has been reduced due to the value of the" " \"Print Sequence\" setting to prevent the gantry from colliding" " with printed models.")).show() def getRaftThickness(self): return self._raft_thickness def _updateRaftThickness(self): old_raft_thickness = self._raft_thickness self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value") self._raft_thickness = 0.0 if self._adhesion_type == "raft": self._raft_thickness = ( self._global_container_stack.getProperty("raft_base_thickness", "value") + self._global_container_stack.getProperty("raft_interface_thickness", "value") + self._global_container_stack.getProperty("raft_surface_layers", "value") * self._global_container_stack.getProperty("raft_surface_thickness", "value") + self._global_container_stack.getProperty("raft_airgap", "value")) # Rounding errors do not matter, we check if raft_thickness has changed at all if old_raft_thickness != self._raft_thickness: self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World) self.raftThicknessChanged.emit() def _onGlobalContainerStackChanged(self): if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._global_container_stack = Application.getInstance().getGlobalContainerStack() if self._global_container_stack: self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged) self._width = self._global_container_stack.getProperty("machine_width", "value") machine_height = self._global_container_stack.getProperty("machine_height", "value") if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time": self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height) if self._height < machine_height: self._buildVolumeMessage() else: self._height = self._global_container_stack.getProperty("machine_height", "value") self._depth = self._global_container_stack.getProperty("machine_depth", "value") self._updateDisallowedAreas() self._updateRaftThickness() self.rebuild() def _onActiveExtruderStackChanged(self): if self._active_extruder_stack: self._active_extruder_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._active_extruder_stack = ExtruderManager.getInstance().getActiveExtruderStack() if self._active_extruder_stack: self._active_extruder_stack.propertyChanged.connect(self._onSettingPropertyChanged) def _onSettingPropertyChanged(self, setting_key, property_name): if property_name != "value": return rebuild_me = False if setting_key == "print_sequence": machine_height = self._global_container_stack.getProperty("machine_height", "value") if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time": self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height) if self._height < machine_height: self._buildVolumeMessage() else: self._height = self._global_container_stack.getProperty("machine_height", "value") rebuild_me = True if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence": self._updateDisallowedAreas() rebuild_me = True if setting_key in self._raft_settings: self._updateRaftThickness() rebuild_me = True if rebuild_me: self.rebuild() def hasErrors(self): return self._has_errors def _updateDisallowedAreas(self): if not self._global_container_stack: return self._has_errors = False # Reset. disallowed_areas = copy.deepcopy( self._global_container_stack.getProperty("machine_disallowed_areas", "value")) areas = [] machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") self._prime_tower_area = None # Add prime tower location as disallowed area. if self._global_container_stack.getProperty("prime_tower_enable", "value") == True: prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value") prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") - machine_width / 2 prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") + machine_depth / 2 '''disallowed_areas.append([ [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], [prime_tower_x - prime_tower_size, prime_tower_y], ])''' self._prime_tower_area = Polygon([ [prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], [prime_tower_x - prime_tower_size, prime_tower_y], ]) # Add extruder prime locations as disallowed areas. # Probably needs some rework after coordinate system change. extruder_manager = ExtruderManager.getInstance() extruders = extruder_manager.getMachineExtruders(self._global_container_stack.getId()) for single_extruder in extruders: extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value") extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value") # TODO: calculate everything in CuraEngine/Firmware/lower left as origin coordinates. # Here we transform the extruder prime pos (lower left as origin) to Cura coordinates # (center as origin, y from back to front) prime_x = extruder_prime_pos_x - machine_width / 2 prime_y = machine_depth / 2 - extruder_prime_pos_y disallowed_areas.append([ [prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], [prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], ]) bed_adhesion_size = self._getBedAdhesionSize(self._global_container_stack) if disallowed_areas: # Extend every area already in the disallowed_areas with the skirt size. for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) areas.append(poly) if self._prime_tower_area: self._prime_tower_area = self._prime_tower_area.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size))) # Add the skirt areas around the borders of the build plate. if bed_adhesion_size > 0: half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2 areas.append(Polygon(numpy.array([ [-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size], [-half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size] ], numpy.float32))) areas.append(Polygon(numpy.array([ [half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [-half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size], [half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size] ], numpy.float32))) # Check if the prime tower area intersects with any of the other areas. # If this is the case, keep the polygon seperate, so it can be drawn in red. # If not, add it back to disallowed area's, so it's rendered as normal. collision = False if self._prime_tower_area: for area in areas: if self._prime_tower_area.intersectsPolygon(area) is not None: collision = True break if not collision: areas.append(self._prime_tower_area) self._prime_tower_area = None self._has_errors = collision self._disallowed_areas = areas ## Convenience function to calculate the size of the bed adhesion in directions x, y. def _getBedAdhesionSize(self, container_stack): skirt_size = 0.0 # If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects if container_stack.getProperty("print_sequence", "value") == "one_at_a_time": return 0.1 # Return a very small value, so we do draw disallowed area's near the edges. adhesion_type = container_stack.getProperty("adhesion_type", "value") if adhesion_type == "skirt": skirt_distance = container_stack.getProperty("skirt_gap", "value") skirt_line_count = container_stack.getProperty("skirt_line_count", "value") skirt_size = skirt_distance + (skirt_line_count * container_stack.getProperty("skirt_brim_line_width", "value")) elif adhesion_type == "brim": skirt_size = container_stack.getProperty("brim_line_count", "value") * container_stack.getProperty("skirt_brim_line_width", "value") elif adhesion_type == "raft": skirt_size = container_stack.getProperty("raft_margin", "value") if container_stack.getProperty("draft_shield_enabled", "value"): skirt_size += container_stack.getProperty("draft_shield_dist", "value") if container_stack.getProperty("xy_offset", "value"): skirt_size += container_stack.getProperty("xy_offset", "value") return skirt_size def _clamp(self, value, min_value, max_value): return max(min(value, max_value), min_value) _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"] _raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"] _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"] _tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
def _updateDisallowedAreas(self): if not self._global_container_stack: return disallowed_areas = copy.deepcopy( self._global_container_stack.getProperty( "machine_disallowed_areas", "value")) areas = [] machine_width = self._global_container_stack.getProperty( "machine_width", "value") machine_depth = self._global_container_stack.getProperty( "machine_depth", "value") # Add prime tower location as disallowed area. if self._global_container_stack.getProperty("prime_tower_enable", "value") == True: prime_tower_size = self._global_container_stack.getProperty( "prime_tower_size", "value") prime_tower_x = self._global_container_stack.getProperty( "prime_tower_position_x", "value") - machine_width / 2 prime_tower_y = -self._global_container_stack.getProperty( "prime_tower_position_y", "value") + machine_depth / 2 disallowed_areas.append([ [ prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size ], [prime_tower_x, prime_tower_y - prime_tower_size], [prime_tower_x, prime_tower_y], [prime_tower_x - prime_tower_size, prime_tower_y], ]) # Add extruder prime locations as disallowed areas. # Probably needs some rework after coordinate system change. extruder_manager = ExtruderManager.getInstance() extruders = extruder_manager.getMachineExtruders( self._global_container_stack.getId()) for single_extruder in extruders: extruder_prime_pos_x = single_extruder.getProperty( "extruder_prime_pos_x", "value") extruder_prime_pos_y = single_extruder.getProperty( "extruder_prime_pos_y", "value") # TODO: calculate everything in CuraEngine/Firmware/lower left as origin coordinates. # Here we transform the extruder prime pos (lower left as origin) to Cura coordinates # (center as origin, y from back to front) prime_x = extruder_prime_pos_x - machine_width / 2 prime_y = machine_depth / 2 - extruder_prime_pos_y disallowed_areas.append([ [prime_x - PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y - PRIME_CLEARANCE], [prime_x + PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], [prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE], ]) bed_adhesion_size = self._getBedAdhesionSize( self._global_container_stack) if disallowed_areas: # Extend every area already in the disallowed_areas with the skirt size. for area in disallowed_areas: poly = Polygon(numpy.array(area, numpy.float32)) poly = poly.getMinkowskiHull( Polygon(approximatedCircleVertices(bed_adhesion_size))) areas.append(poly) # Add the skirt areas around the borders of the build plate. if bed_adhesion_size > 0: half_machine_width = self._global_container_stack.getProperty( "machine_width", "value") / 2 half_machine_depth = self._global_container_stack.getProperty( "machine_depth", "value") / 2 areas.append( Polygon( numpy.array([[-half_machine_width, -half_machine_depth], [-half_machine_width, half_machine_depth], [ -half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size ], [ -half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size ]], numpy.float32))) areas.append( Polygon( numpy.array([[half_machine_width, half_machine_depth], [half_machine_width, -half_machine_depth], [ half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size ], [ half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size ]], numpy.float32))) areas.append( Polygon( numpy.array([[-half_machine_width, half_machine_depth], [half_machine_width, half_machine_depth], [ half_machine_width - bed_adhesion_size, half_machine_depth - bed_adhesion_size ], [ -half_machine_width + bed_adhesion_size, half_machine_depth - bed_adhesion_size ]], numpy.float32))) areas.append( Polygon( numpy.array([[half_machine_width, -half_machine_depth], [-half_machine_width, -half_machine_depth], [ -half_machine_width + bed_adhesion_size, -half_machine_depth + bed_adhesion_size ], [ half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size ]], numpy.float32))) self._disallowed_areas = areas