def _computeDisallowedAreasPrime(self, border_size, used_extruders): result = {} machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") for extruder in used_extruders: prime_x = extruder.getProperty("extruder_prime_pos_x", "value") prime_y = - extruder.getProperty("extruder_prime_pos_y", "value") #Ignore extruder prime position if it is not set if prime_x == 0 and prime_y == 0: result[extruder.getId()] = [] continue if not self._global_container_stack.getProperty("machine_center_is_zero", "value"): prime_x = prime_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left. prime_y = prime_y + machine_depth / 2 prime_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE) prime_polygon = prime_polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size)) prime_polygon = prime_polygon.translate(prime_x, prime_y) result[extruder.getId()] = [prime_polygon] return result
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 _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders): result = {} machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") for extruder in used_extruders: prime_blob_enabled = extruder.getProperty("prime_blob_enable", "value") prime_x = extruder.getProperty("extruder_prime_pos_x", "value") prime_y = - extruder.getProperty("extruder_prime_pos_y", "value") #Ignore extruder prime position if it is not set or if blob is disabled if (prime_x == 0 and prime_y == 0) or not prime_blob_enabled: result[extruder.getId()] = [] continue if not self._global_container_stack.getProperty("machine_center_is_zero", "value"): prime_x = prime_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left. prime_y = prime_y + machine_depth / 2 prime_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE) prime_polygon = prime_polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size)) prime_polygon = prime_polygon.translate(prime_x, prime_y) result[extruder.getId()] = [prime_polygon] 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 _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 _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 _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 _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 test_intersectConvexHull(self, data): p1 = Polygon(numpy.array(data["p1"])) p2 = Polygon(numpy.array(data["p2"])) result = p1.intersectionConvexHulls(p2) assert len(result.getPoints()) == len( data["answer"]) #Same amount of vertices. isCorrect = False for rotation in range( 0, len(result.getPoints()) ): #The order of vertices doesn't matter, so rotate the result around and if any check succeeds, the answer is correct. thisCorrect = True #Is this rotation correct? for vertex in range(0, len(result.getPoints())): for dimension in range(0, len(result.getPoints()[vertex])): if not Float.fuzzyCompare( result.getPoints()[vertex][dimension], data["answer"][vertex][dimension]): thisCorrect = False break #Break out of two loops. if not thisCorrect: break if thisCorrect: #All vertices checked and it's still correct. isCorrect = True break result.setPoints( numpy.roll(result.getPoints(), 1, axis=0)) #Perform the rotation for the next check. assert isCorrect
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 test_getConvexHullPrintingMesh(convex_hull_decorator): node = SceneNode() node.addDecorator(PrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) convex_hull_decorator._compute2DConvexHull = MagicMock(return_value = Polygon.approximatedCircle(10)) assert convex_hull_decorator.getConvexHull() == Polygon.approximatedCircle(10)
def test_translate(self): polygon = Polygon( numpy.array([[0.0, 0.0], [2.0, 0.0], [1.0, 2.0]], numpy.float32)) translated_poly = polygon.translate(2, 3) result = Polygon( numpy.array([[2.0, 3.0], [4.0, 3.0], [3.0, 5.0]], numpy.float32)) assert result == translated_poly
def _getHeadAndFans(self) -> Polygon: if not self._global_stack: return Polygon() polygon = Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32)) offset_x = self._getSettingProperty("machine_nozzle_offset_x", "value") offset_y = self._getSettingProperty("machine_nozzle_offset_y", "value") return polygon.translate(-offset_x, -offset_y)
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_mirror(self, data): polygon = Polygon(numpy.array(data["points"], numpy.float32)) #Create a polygon with the specified points. polygon.mirror(data["axis_point"], data["axis_direction"]) #Mirror over the specified axis. points = polygon.getPoints() assert len(points) == len(data["points"]) #Must have the same amount of vertices. for point_index in range(len(points)): assert len(points[point_index]) == len(data["answer"][point_index]) #Same dimensionality (2). for dimension in range(len(points[point_index])): assert Float.fuzzyCompare(points[point_index][dimension], data["answer"][point_index][dimension]) #All points must be equal.
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 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_getConvexHullPrintingMesh(convex_hull_decorator): node = SceneNode() node.addDecorator(PrintingDecorator()) with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) convex_hull_decorator._compute2DConvexHull = MagicMock( return_value=Polygon.approximatedCircle(10)) assert convex_hull_decorator.getConvexHull() == Polygon.approximatedCircle( 10)
def test_mirrorDiagonal(self): p = Polygon(numpy.array([ #Make a triangle. [0.0, 0.0], [2.0, 0.0], [1.0, 2.0] ], numpy.float32)) p.mirror([0, 4], [1, 1]) #Mirror over the diagonal axis. The diagonal axis also has an offset. self.assertEqual(len(p), 3) self.assertEqual(p[0], [-4.0, 4.0]) self.assertEqual(p[1], [-4.0, 6.0]) self.assertEqual(p[2], [-2.0, 5.0])
def test_mirrorHorizontalFar(self): p = Polygon(numpy.array([ #Make a triangle. [0.0, 0.0], [2.0, 0.0], [1.0, 2.0] ], numpy.float32)) p.mirror([10, 0], [0, 1]) #Mirror over the vertical axis with an offset. self.assertEqual(len(p), 3) self.assertEqual(p[0], [20.0, 0.0]) self.assertEqual(p[1], [18.0, 0.0]) self.assertEqual(p[2], [19.0, 2.0])
def test_mirrorVertical(self): p = Polygon(numpy.array([ #Make a triangle. [0.0, 0.0], [2.0, 0.0], [1.0, 2.0] ], numpy.float32)) p.mirror([0, 0], [1, 0]) #Mirror over the horizontal axis. self.assertEqual(len(p), 3) self.assertEqual(p[0], [0.0, 0.0]) self.assertEqual(p[1], [2.0, 0.0]) self.assertEqual(p[2], [1.0, -2.0])
def test_project(self, data): p = Polygon(numpy.array([ [0.0, 1.0], [1.0, 1.0], [1.0, 2.0], [0.0, 2.0] ], numpy.float32)) result = p.project(data["normal"]) #Project the polygon onto the specified normal vector. assert len(result) == len(data["answer"]) #Same dimensionality (2). for dimension in range(len(result)): assert Float.fuzzyCompare(result[dimension], data["answer"][dimension])
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: 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 _computeDisallowedAreasPrime(self, border_size, used_extruders): result = {} machine_width = self._global_container_stack.getProperty("machine_width", "value") machine_depth = self._global_container_stack.getProperty("machine_depth", "value") for extruder in used_extruders: prime_x = extruder.getProperty("extruder_prime_pos_x", "value") - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left. prime_y = machine_depth / 2 - extruder.getProperty("extruder_prime_pos_y", "value") prime_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE) prime_polygon = prime_polygon.translate(prime_x, prime_y) prime_polygon = prime_polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size)) result[extruder.getId()] = [prime_polygon] return result
def _add2DAdhesionMargin(self, poly): # Compensate for raft/skirt/brim # Add extra margin depending on adhesion type adhesion_type = self._global_stack.getProperty("adhesion_type", "value") extra_margin = 0 machine_head_coords = numpy.array( self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32) head_y_size = abs(machine_head_coords).min() # safe margin to take off in all directions if adhesion_type == "raft": extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value") - head_y_size) elif adhesion_type == "brim": extra_margin = max(0, self._global_stack.getProperty("brim_width", "value") - head_y_size) elif adhesion_type == "skirt": extra_margin = max( 0, self._global_stack.getProperty("skirt_gap", "value") + self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value") - head_y_size) # adjust head_and_fans with extra margin if extra_margin > 0: # In Cura 2.2+, there is a function to create this circle-like polygon. extra_margin_polygon = Polygon(numpy.array([ [-extra_margin, 0], [-extra_margin * 0.707, extra_margin * 0.707], [0, extra_margin], [extra_margin * 0.707, extra_margin * 0.707], [extra_margin, 0], [extra_margin * 0.707, -extra_margin * 0.707], [0, -extra_margin], [-extra_margin * 0.707, -extra_margin * 0.707] ], numpy.float32)) poly = poly.getMinkowskiHull(extra_margin_polygon) return poly
def test_compute2DConvexHullNoMeshData(convex_hull_decorator): node = SceneNode() with patch("UM.Application.Application.getInstance", MagicMock(return_value=mocked_application)): convex_hull_decorator.setNode(node) assert convex_hull_decorator._compute2DConvexHull() == Polygon([])
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8): arranger = Arrange(x, y, x // 2, y // 2, scale = scale) arranger.centerFirst() if fixed_nodes is None: fixed_nodes = [] for node_ in DepthFirstIterator(scene_root): # Only count sliceable objects if node_.callDecoration("isSliceable"): fixed_nodes.append(node_) # Place all objects fixed nodes for fixed_node in fixed_nodes: vertices = fixed_node.callDecoration("getConvexHullHead") or fixed_node.callDecoration("getConvexHull") if not vertices: continue vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset)) points = copy.deepcopy(vertices._points) # After scaling (like up to 0.1 mm) the node might not have points if not points.size: continue shape_arr = ShapeArray.fromPolygon(points, scale = scale) arranger.place(0, 0, shape_arr) # If a build volume was set, add the disallowed areas if Arrange.build_volume: disallowed_areas = Arrange.build_volume.getDisallowedAreasNoBrim() for area in disallowed_areas: points = copy.deepcopy(area._points) shape_arr = ShapeArray.fromPolygon(points, scale = scale) arranger.place(0, 0, shape_arr, update_empty = False) return arranger
def _onActiveMachineChanged(self): machine = self.getActiveMachine() if machine: Preferences.getInstance().setValue("cura/active_machine", machine.getName()) self._volume.setWidth( machine.getSettingValueByKey("machine_width")) self._volume.setHeight( machine.getSettingValueByKey("machine_height")) self._volume.setDepth( machine.getSettingValueByKey("machine_depth")) disallowed_areas = machine.getSettingValueByKey( "machine_disallowed_areas") areas = [] if disallowed_areas: for area in disallowed_areas: areas.append(Polygon(numpy.array(area, numpy.float32))) self._volume.setDisallowedAreas(areas) self._volume.rebuild() offset = machine.getSettingValueByKey("machine_platform_offset") if offset: self._platform.setPosition( Vector(offset[0], offset[1], offset[2])) else: self._platform.setPosition(Vector(0.0, 0.0, 0.0))
def getConvexHull(self): if self._node is None: return None if getattr(self._node, "_non_printing_mesh", False): # infill_mesh, cutting_mesh and anti_overhang_mesh do not need a convex hull # node._non_printing_mesh is set in SettingOverrideDecorator return None hull = self._compute2DConvexHull() if self._global_stack and self._node: # Parent can be None if node is just loaded. if self._global_stack.getProperty( "print_sequence", "value") == "one_at_a_time" and ( self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")): hull = hull.getMinkowskiHull( Polygon( numpy.array( self._global_stack.getProperty( "machine_head_polygon", "value"), numpy.float32))) hull = self._add2DAdhesionMargin(hull) return hull
def fromNode(cls, node, min_offset, scale=0.5): 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 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 create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8): arranger = Arrange(x, y, x // 2, y // 2, scale = scale) arranger.centerFirst() if fixed_nodes is None: fixed_nodes = [] for node_ in DepthFirstIterator(scene_root): # Only count sliceable objects if node_.callDecoration("isSliceable"): fixed_nodes.append(node_) # Place all objects fixed nodes for fixed_node in fixed_nodes: vertices = fixed_node.callDecoration("getConvexHullHead") or fixed_node.callDecoration("getConvexHull") if not vertices: continue vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset)) points = copy.deepcopy(vertices._points) # After scaling (like up to 0.1 mm) the node might not have points if len(points) == 0: continue shape_arr = ShapeArray.fromPolygon(points, scale = scale) arranger.place(0, 0, shape_arr) # If a build volume was set, add the disallowed areas if Arrange.build_volume: disallowed_areas = Arrange.build_volume.getDisallowedAreasNoBrim() for area in disallowed_areas: points = copy.deepcopy(area._points) shape_arr = ShapeArray.fromPolygon(points, scale = scale) arranger.place(0, 0, shape_arr, update_empty = False) return arranger
def create(cls, scene_root=None, fixed_nodes=None, scale=0.5, x=350, y=250, min_offset=8) -> "Arrange": """Helper to create an :py:class:`cura.Arranging.Arrange.Arrange` instance Either fill in scene_root and create will find all sliceable nodes by itself, or use fixed_nodes to provide the nodes yourself. :param scene_root: Root for finding all scene nodes default = None :param fixed_nodes: Scene nodes to be placed default = None :param scale: default = 0.5 :param x: default = 350 :param y: default = 250 :param min_offset: default = 8 """ arranger = Arrange(x, y, x // 2, y // 2, scale=scale) arranger.centerFirst() if fixed_nodes is None: fixed_nodes = [] for node_ in DepthFirstIterator(scene_root): # Only count sliceable objects if node_.callDecoration("isSliceable"): fixed_nodes.append(node_) # Place all objects fixed nodes for fixed_node in fixed_nodes: vertices = fixed_node.callDecoration( "getConvexHullHead") or fixed_node.callDecoration( "getConvexHull") if not vertices: continue vertices = vertices.getMinkowskiHull( Polygon.approximatedCircle(min_offset)) points = copy.deepcopy(vertices._points) # After scaling (like up to 0.1 mm) the node might not have points if not points.size: continue try: shape_arr = ShapeArray.fromPolygon(points, scale=scale) except ValueError: Logger.logException("w", "Unable to create polygon") continue arranger.place(0, 0, shape_arr) # If a build volume was set, add the disallowed areas if Arrange.build_volume: disallowed_areas = Arrange.build_volume.getDisallowedAreasNoBrim() for area in disallowed_areas: points = copy.deepcopy(area._points) shape_arr = ShapeArray.fromPolygon(points, scale=scale) arranger.place(0, 0, shape_arr, update_empty=False) return arranger
def test_singleExtruder(self, build_volume: BuildVolume): mocked_global_stack = MagicMock(name = "mocked_global_stack") mocked_global_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect) mocked_extruder_stack = MagicMock(name = "mocked_extruder_stack") mocked_extruder_stack.getId = MagicMock(return_value = "0") mocked_extruder_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect) build_volume._global_container_stack = mocked_global_stack # Create a polygon that should be the result resulting_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE) # Since we want a blob of size 12; resulting_polygon = resulting_polygon.getMinkowskiHull(Polygon.approximatedCircle(12)) # In the The translation result is 25, -50 (due to the settings used) resulting_polygon = resulting_polygon.translate(25, -50) assert build_volume._computeDisallowedAreasPrimeBlob(12, [mocked_extruder_stack]) == {"0": [resulting_polygon]}
def test_project(self): p = Polygon( numpy.array([[0.0, 1.0], [1.0, 1.0], [1.0, 2.0], [0.0, 2.0]], numpy.float32)) normal = numpy.array([0.0, 1.0]) self.assertEqual((1.0, 2.0), p.project(normal)) normal = numpy.array([1.0, 0.0]) self.assertEqual((0.0, 1.0), p.project(normal)) normal = numpy.array([math.sqrt(0.5), math.sqrt(0.5)]) result = p.project(normal) self.assertTrue(Float.fuzzyCompare(result[0], 0.70710678), "{0} does not equal {1}".format(result[0], 0.70710678)) self.assertTrue(Float.fuzzyCompare(result[1], 2.12132034), "{0} does not equal {1}".format(result[1], 2.12132034))
def verifyIntersection(self, p1, p2, required_result): for n in range(0, 4): for m in range(0, 4): result = p1.intersectsPolygon(p2) self.assertEqual(result, required_result) p2 = Polygon( numpy.concatenate( (p2.getPoints()[1:], p2.getPoints()[0:1]))) p1 = Polygon( numpy.concatenate((p1.getPoints()[1:], p1.getPoints()[0:1])))
def test_intersectsPolygon(self): p1 = Polygon( numpy.array([[0, 0], [10, 0], [10, 10], [0, 10]], numpy.float32)) p2 = Polygon( numpy.array([[5, 0], [15, 0], [15, 10], [5, 10]], numpy.float32)) self.verifyIntersection(p1, p2, (-5.0, 0.0)) p2 = Polygon( numpy.array([[-5, 0], [5, 0], [5, 10], [-5, 10]], numpy.float32)) self.verifyIntersection(p1, p2, (5.0, 0.0)) p2 = Polygon( numpy.array([[0, 5], [10, 5], [10, 15], [0, 15]], numpy.float32)) self.verifyIntersection(p1, p2, (0.0, -5.0)) p2 = Polygon( numpy.array([[0, -5], [10, -5], [10, 5], [0, 5]], numpy.float32)) self.verifyIntersection(p1, p2, (0.0, 5.0)) p2 = Polygon( numpy.array([[5, 5], [15, -5], [30, 5], [15, 15]], numpy.float32)) self.verifyIntersection(p1, p2, (-5.0, 0.0)) p2 = Polygon( numpy.array([[15, 0], [25, 0], [25, 10], [15, 10]], numpy.float32)) self.verifyIntersection(p1, p2, None)
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 test_project(self): p = Polygon(numpy.array([ [0.0, 1.0], [1.0, 1.0], [1.0, 2.0], [0.0, 2.0] ], numpy.float32)) normal = numpy.array([0.0, 1.0]) self.assertEqual((1.0, 2.0), p.project(normal)) normal = numpy.array([1.0, 0.0]) self.assertEqual((0.0, 1.0), p.project(normal)) normal = numpy.array([math.sqrt(0.5), math.sqrt(0.5)]) result = p.project(normal) self.assertTrue(Float.fuzzyCompare(result[0], 0.70710678), "{0} does not equal {1}".format(result[0], 0.70710678)) self.assertTrue(Float.fuzzyCompare(result[1], 2.12132034), "{0} does not equal {1}".format(result[1], 2.12132034))
def test_intersectConvexHull(self, data): p1 = Polygon(numpy.array(data["p1"])) p2 = Polygon(numpy.array(data["p2"])) result = p1.intersectionConvexHulls(p2) assert len(result.getPoints()) == len(data["answer"]) #Same amount of vertices. isCorrect = False for rotation in range(0, len(result.getPoints())): #The order of vertices doesn't matter, so rotate the result around and if any check succeeds, the answer is correct. thisCorrect = True #Is this rotation correct? for vertex in range(0, len(result.getPoints())): for dimension in range(0, len(result.getPoints()[vertex])): if not Float.fuzzyCompare(result.getPoints()[vertex][dimension], data["answer"][vertex][dimension]): thisCorrect = False break #Break out of two loops. if not thisCorrect: break if thisCorrect: #All vertices checked and it's still correct. isCorrect = True break result.setPoints(numpy.roll(result.getPoints(), 1, axis = 0)) #Perform the rotation for the next check. assert isCorrect
def test_intersectsPolygon(self, data): p1 = Polygon(numpy.array([ #The base polygon to intersect with. [ 0, 0], [10, 0], [10, 10], [ 0, 10] ], numpy.float32)) p2 = Polygon(numpy.array(data["polygon"])) #The parametrised polygon to intersect with. #Shift the order of vertices in both polygons around. The outcome should be independent of what the first vertex is. for n in range(0, len(p1.getPoints())): for m in range(0, len(data["polygon"])): result = p1.intersectsPolygon(p2) if not data["answer"]: #Result should be None. assert result == None else: assert result != None for i in range(0, len(data["answer"])): assert Float.fuzzyCompare(result[i], data["answer"][i]) p2.setPoints(numpy.roll(p2.getPoints(), 1, axis = 0)) #Shift p2. p1.setPoints(numpy.roll(p1.getPoints(), 1, axis = 0)) #Shift p1.
def verifyIntersection(self, p1, p2, required_result): for n in range(0, 4): for m in range(0, 4): result = p1.intersectsPolygon(p2) self.assertEqual(result, required_result) p2 = Polygon(numpy.concatenate((p2.getPoints()[1:], p2.getPoints()[0:1]))) p1 = Polygon(numpy.concatenate((p1.getPoints()[1:], p1.getPoints()[0:1])))
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 _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): transform = node._transformation transform_x = transform._data[0][3] transform_y = transform._data[2][3] hull_verts = node.callDecoration("getConvexHull") # For one_at_a_time printing you need the convex hull head. hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts 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 _compute2DConvexHull(self): if self._node.callDecoration("isGroup"): points = numpy.zeros((0, 2), dtype=numpy.int32) for child in self._node.getChildren(): child_hull = child.callDecoration("_compute2DConvexHull") if child_hull: points = numpy.append(points, child_hull.getPoints(), axis = 0) if points.size < 3: return None child_polygon = Polygon(points) # Check the cache if child_polygon == self._2d_convex_hull_group_child_polygon: return self._2d_convex_hull_group_result # First, calculate the normal convex hull around the points convex_hull = child_polygon.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. rounded_hull = self._roundHull(convex_hull) # Store the result in the cache self._2d_convex_hull_group_child_polygon = child_polygon self._2d_convex_hull_group_result = rounded_hull return rounded_hull else: rounded_hull = None mesh = None world_transform = None if self._node.getMeshData(): mesh = self._node.getMeshData() world_transform = self._node.getWorldTransformation() # Check the cache if mesh is self._2d_convex_hull_mesh and world_transform == self._2d_convex_hull_mesh_world_transform: return self._2d_convex_hull_mesh_result vertex_data = mesh.getConvexHullTransformedVertices(world_transform) # Don't use data below 0. # TODO; We need a better check for this as this gives poor results for meshes with long edges. # Do not throw away vertices: the convex hull may be too small and objects can collide. # vertex_data = vertex_data[vertex_data[:,1] >= -0.01] if len(vertex_data) >= 4: # 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) if len(vertex_data) >= 4: # First, calculate the normal convex hull around the points convex_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. rounded_hull = convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32))) # Store the result in the cache self._2d_convex_hull_mesh = mesh self._2d_convex_hull_mesh_world_transform = world_transform self._2d_convex_hull_mesh_result = rounded_hull return rounded_hull
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._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 test_mirrorEmpty(self): p = Polygon(numpy.array([], numpy.float32)) #Empty polygon. p.mirror([0, 0], [1, 0]) #Mirror over the horizontal axis. self.assertEqual(len(p), 0)
def test_mirrorSingleVertex(self): p = Polygon(numpy.array([[10.0, 0.0]], numpy.float32)) #Single vertex. p.mirror([0, 0], [1, 0]) #Mirror over the horizontal axis. self.assertEqual(len(p), 1) self.assertEqual(p[0], [-10.0, 0.0])