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 _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 _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 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 _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 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 _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 _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 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 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 _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 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 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_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(): 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(): """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 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 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 test_equalityString(self): polygon = Polygon.approximatedCircle(42) assert polygon != "42" # Not the answer after all!
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_equality(self): polygon_1 = Polygon.approximatedCircle(2) polygon_2 = Polygon.approximatedCircle(2) assert polygon_1 == polygon_2
def run(self): if self._build_plate_number is None: self.setResult(StartJobResult.Error) return stack = Application.getInstance().getGlobalContainerStack() if not stack: self.setResult(StartJobResult.Error) return # Don't slice if there is a setting with an error value. if Application.getInstance().getMachineManager().stacksHaveErrors: self.setResult(StartJobResult.SettingError) return if Application.getInstance().getBuildVolume().hasErrors(): self.setResult(StartJobResult.BuildPlateError) return # Don't slice if the buildplate or the nozzle type is incompatible with the materials if not Application.getInstance().getMachineManager().variantBuildplateCompatible and \ not Application.getInstance().getMachineManager().variantBuildplateUsable: self.setResult(StartJobResult.MaterialIncompatible) return for position, extruder_stack in stack.extruders.items(): material = extruder_stack.findContainer({"type": "material"}) if not extruder_stack.isEnabled: continue if material: if material.getMetaDataEntry("compatible") == False: self.setResult(StartJobResult.MaterialIncompatible) return # Don't slice if there is a per object setting with an error value. for node in DepthFirstIterator(self._scene.getRoot()): if not isinstance(node, CuraSceneNode) or not node.isSelectable(): continue if self._checkStackForErrors(node.callDecoration("getStack")): self.setResult(StartJobResult.ObjectSettingError) return with self._scene.getSceneLock(): # Remove old layer data. for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("getLayerData") and node.callDecoration( "getBuildPlateNumber") == self._build_plate_number: node.getParent().removeChild(node) break # Get the objects in their groups to print. object_groups = [] if stack.getProperty("print_sequence", "value") == "one_at_a_time": for node in OneAtATimeIterator(self._scene.getRoot()): temp_list = [] # Node can't be printed, so don't bother sending it. if getattr(node, "_outside_buildarea", False): continue # Filter on current build plate build_plate_number = node.callDecoration( "getBuildPlateNumber") if build_plate_number is not None and build_plate_number != self._build_plate_number: continue children = node.getAllChildren() children.append(node) for child_node in children: if child_node.getMeshData() and child_node.getMeshData( ).getVertices() is not None: temp_list.append(child_node) if temp_list: object_groups.append(temp_list) Job.yieldThread() if len(object_groups) == 0: Logger.log( "w", "No objects suitable for one at a time found, or no correct order found" ) else: temp_list = [] has_printing_mesh = False # print convex hull nodes as "faux-raft" print_convex_hulls = stack.getProperty("blackbelt_raft", "value") for node in DepthFirstIterator(self._scene.getRoot()): slice_node = (print_convex_hulls and type(node) is ConvexHullNode ) or node.callDecoration("isSliceable") if slice_node and node.getMeshData() and node.getMeshData( ).getVertices() is not None: per_object_stack = node.callDecoration("getStack") is_non_printing_mesh = False if per_object_stack: is_non_printing_mesh = any( per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) # Find a reason not to add the node if node.callDecoration( "getBuildPlateNumber" ) != self._build_plate_number and type( node) is not ConvexHullNode: # NB: ConvexHullNodes get none of the usual decorators, so skip checking for them continue if getattr(node, "_outside_buildarea", False) and not is_non_printing_mesh: continue temp_list.append(node) if not is_non_printing_mesh: has_printing_mesh = True Job.yieldThread() #If the list doesn't have any model with suitable settings then clean the list # otherwise CuraEngine will crash if not has_printing_mesh: temp_list.clear() if temp_list: object_groups.append(temp_list) extruders_enabled = { position: stack.isEnabled for position, stack in Application.getInstance(). getGlobalContainerStack().extruders.items() } filtered_object_groups = [] for group in object_groups: stack = Application.getInstance().getGlobalContainerStack() skip_group = False for node in group: # ConvexHullNodes get none of the usual decorators. If it made it here, it is meant to be printed if type( node ) is not ConvexHullNode and not extruders_enabled[ node.callDecoration("getActiveExtruderPosition")]: skip_group = True break if not skip_group: filtered_object_groups.append(group) # There are cases when there is nothing to slice. This can happen due to one at a time slicing not being # able to find a possible sequence or because there are no objects on the build plate (or they are outside # the build volume) if not filtered_object_groups: self.setResult(StartJobResult.NothingToSlice) return container_registry = ContainerRegistry.getInstance() stack_id = stack.getId() # Adapt layer_height and material_flow for a slanted gantry gantry_angle = self._scene.getRoot().callDecoration( "getGantryAngle") if gantry_angle: # not 0 or None # Act on a copy of the stack, so these changes don't cause a reslice _stack = CuraContainerStack(stack_id + "_temp") index = 0 for container in stack.getContainers(): if container_registry.isReadOnly(container.getId()): _stack.replaceContainer(index, container) else: _stack.replaceContainer(index, copy.deepcopy(container)) index = index + 1 stack = _stack # Make sure CuraEngine does not create any supports # support_enable is set in the frontend so support options are settable, # but CuraEngine support structures don't work for slanted gantry stack.setProperty("support_enable", "value", False) # Make sure CuraEngine does not create a raft (we create one manually) # Adhsion type is used in the frontend to show the raft in the viewport stack.setProperty("adhesion_type", "value", "none") for key in ["layer_height", "layer_height_0"]: current_value = stack.getProperty(key, "value") stack.setProperty(key, "value", current_value / math.sin(gantry_angle)) self._buildGlobalSettingsMessage(stack) self._buildGlobalInheritsStackMessage(stack) # Build messages for extruder stacks for position, extruder_stack in Application.getInstance( ).getGlobalContainerStack().extruders.items(): if gantry_angle: # not 0 or None # Act on a copy of the stack, so these changes don't cause a reslice _extruder_stack = CuraContainerStack( extruder_stack.getId() + "_temp") index = 0 for container in extruder_stack.getContainers(): if container_registry.isReadOnly(container.getId()): _extruder_stack.replaceContainer(index, container) else: _extruder_stack.replaceContainer( index, copy.deepcopy(container)) index = index + 1 extruder_stack = _extruder_stack extruder_stack.setNextStack(stack) for key in [ "material_flow", "prime_tower_flow", "spaghetti_flow" ]: if extruder_stack.hasProperty(key, "value"): current_value = extruder_stack.getProperty( key, "value") extruder_stack.setProperty( key, "value", current_value * math.sin(gantry_angle)) self._buildExtruderMessage(extruder_stack) bottom_cutting_meshes = [] raft_meshes = [] if gantry_angle: # not 0 or None # Add a modifier mesh to all printable meshes touching the belt for group in filtered_object_groups: added_meshes = [] for object in group: is_non_printing_mesh = False per_object_stack = object.callDecoration("getStack") if per_object_stack: is_non_printing_mesh = any( per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) # ConvexHullNodes get none of the usual decorators. If it made it here, it is meant to be printed if type(object) is ConvexHullNode: raft_thickness = stack.getProperty( "blackbelt_raft_thickness", "value") raft_margin = stack.getProperty( "blackbelt_raft_margin", "value") mb = MeshBuilder() hull_polygon = object.getHull() if raft_margin > 0: hull_polygon = hull_polygon.getMinkowskiHull( Polygon.approximatedCircle(raft_margin)) mb.addConvexPolygonExtrusion( hull_polygon.getPoints()[::-1], 0, raft_thickness) new_node = self._addMesh(mb, "raftMesh") added_meshes.append(new_node) raft_meshes.append(new_node.getName()) elif not is_non_printing_mesh: extruder_stack_index = object.callDecoration( "getActiveExtruderPosition") if not extruder_stack_index: extruder_stack_index = 0 extruder_stack = ExtruderManager.getInstance( ).getMachineExtruders(Application.getInstance( ).getGlobalContainerStack().getId())[int( extruder_stack_index)] aabb = object.getBoundingBox() if aabb.bottom < 0: # mesh extends below the belt; add a cutting mesh to cut off the part below the bottom height = -aabb.bottom center = Vector(aabb.center.x, -height / 2, aabb.center.z) mb = MeshBuilder() mb.addCube(width=aabb.width, height=height, depth=aabb.depth, center=center) new_node = self._addMesh( mb, "bottomCuttingMesh") added_meshes.append(new_node) bottom_cutting_meshes.append( new_node.getName()) if added_meshes: group += added_meshes transform_matrix = self._scene.getRoot().callDecoration( "getTransformMatrix") front_offset = None raft_offset = 0 raft_speed = None raft_flow = 1.0 if stack.getProperty("blackbelt_raft", "value"): raft_offset = stack.getProperty( "blackbelt_raft_thickness", "value") + stack.getProperty( "blackbelt_raft_gap", "value") raft_speed = stack.getProperty("blackbelt_raft_speed", "value") raft_flow = stack.getProperty("blackbelt_raft_flow", "value") * math.sin(gantry_angle) for group in filtered_object_groups: group_message = self._slice_message.addRepeatedMessage( "object_lists") if group[0].getParent() is not None and group[0].getParent( ).callDecoration("isGroup"): self._handlePerObjectSettings(group[0].getParent(), group_message) for object in group: if type(object) is ConvexHullNode: continue mesh_data = object.getMeshData() rot_scale = object.getWorldTransformation().getTransposed( ).getData()[0:3, 0:3] translate = object.getWorldTransformation().getData()[:3, 3] # offset all non-raft objects if rafts are enabled # air gap is applied here to vertically offset objects from the raft if object.getName() not in raft_meshes: translate[1] = translate[1] + raft_offset # This effectively performs a limited form of MeshData.getTransformed that ignores normals. verts = mesh_data.getVertices() verts = verts.dot(rot_scale) verts += translate if transform_matrix: verts = transformVertices(verts, transform_matrix) is_non_printing_mesh = object.getName( ) in bottom_cutting_meshes or object.getName( ) in raft_meshes if not is_non_printing_mesh: per_object_stack = object.callDecoration( "getStack") if per_object_stack: is_non_printing_mesh = any( per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) if not is_non_printing_mesh: _front_offset = verts[:, 1].min() if front_offset is None or _front_offset < front_offset: front_offset = _front_offset # Convert from Y up axes to Z up axes. Equals a 90 degree rotation. verts[:, [1, 2]] = verts[:, [2, 1]] verts[:, 1] *= -1 obj = group_message.addRepeatedMessage("objects") obj.id = id(object) indices = mesh_data.getIndices() if indices is not None: flat_verts = numpy.take(verts, indices.flatten(), axis=0) else: flat_verts = numpy.array(verts) obj.vertices = flat_verts if object.getName() in raft_meshes: self._addSettingsMessage( obj, { "wall_line_count": 99999999, "speed_wall_0": raft_speed, "speed_wall_x": raft_speed, "material_flow": raft_flow }) elif object.getName() in bottom_cutting_meshes: self._addSettingsMessage( obj, { "cutting_mesh": True, "wall_line_count": 0, "top_layers": 0, "bottom_layers": 0, "infill_line_distance": 0 }) else: self._handlePerObjectSettings(object, obj) Job.yieldThread() # Store the front-most coordinate of the scene so the scene can be moved back into place post slicing # TODO: this should be handled per mesh-group instead of per scene # One-at-a-time printing should be disabled for slanted gantry printers for now self._scene.getRoot().callDecoration("setSceneFrontOffset", front_offset) self.setResult(StartJobResult.Finished)
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 _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 _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 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 test_equalitySamePolygon(self): polygon = Polygon.approximatedCircle(2) assert polygon == polygon
def test_isInside(self): polygon = Polygon.approximatedCircle(5) assert polygon.isInside((0, 0)) assert not polygon.isInside((9001, 9001))
def test_inequality(self): # This case should short cirquit because the length of points are not the same polygon_1 = Polygon.approximatedCircle(2) polygon_2 = Polygon() assert polygon_1 != polygon_2
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 run(self) -> None: if self._build_plate_number is None: self.setResult(StartJobResult.Error) return stack = CuraApplication.getInstance().getGlobalContainerStack() if not stack: self.setResult(StartJobResult.Error) return # Don't slice if there is a setting with an error value. if CuraApplication.getInstance().getMachineManager().stacksHaveErrors: self.setResult(StartJobResult.SettingError) return if CuraApplication.getInstance().getBuildVolume().hasErrors(): self.setResult(StartJobResult.BuildPlateError) return # Don't slice if the buildplate or the nozzle type is incompatible with the materials if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \ not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable: self.setResult(StartJobResult.MaterialIncompatible) return for position, extruder_stack in stack.extruders.items(): material = extruder_stack.findContainer({"type": "material"}) if not extruder_stack.isEnabled: continue if material: if material.getMetaDataEntry("compatible") == False: self.setResult(StartJobResult.MaterialIncompatible) return # Don't slice if there is a per object setting with an error value. for node in DepthFirstIterator( self._scene.getRoot() ): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if not isinstance(node, CuraSceneNode) or not node.isSelectable(): continue if self._checkStackForErrors(node.callDecoration("getStack")): self.setResult(StartJobResult.ObjectSettingError) return with self._scene.getSceneLock(): # Remove old layer data. for node in DepthFirstIterator( self._scene.getRoot() ): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("getLayerData") and node.callDecoration( "getBuildPlateNumber") == self._build_plate_number: node.getParent().removeChild(node) break global_enable_support = stack.getProperty("support_enable", "value") # Get the objects in their groups to print. object_groups = [] if stack.getProperty("print_sequence", "value") == "one_at_a_time": # note that one_at_a_time printing is disabled on belt printers due to collission risk for node in OneAtATimeIterator( self._scene.getRoot() ): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. temp_list = [] # Node can't be printed, so don't bother sending it. if getattr(node, "_outside_buildarea", False): continue # Filter on current build plate build_plate_number = node.callDecoration( "getBuildPlateNumber") if build_plate_number is not None and build_plate_number != self._build_plate_number: continue children = node.getAllChildren() children.append(node) for child_node in children: if child_node.getMeshData() and child_node.getMeshData( ).getVertices() is not None: temp_list.append(child_node) if temp_list: object_groups.append(temp_list) Job.yieldThread() if len(object_groups) == 0: Logger.log( "w", "No objects suitable for one at a time found, or no correct order found" ) else: temp_list = [] has_printing_mesh = False # print convex hull nodes as "faux-raft" print_convex_hulls = self._preferences.getValue( "BeltPlugin/raft") for node in DepthFirstIterator( self._scene.getRoot() ): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. slice_node = (print_convex_hulls and type(node) is ConvexHullNode ) or node.callDecoration("isSliceable") if slice_node and node.getMeshData() and node.getMeshData( ).getVertices() is not None: per_object_stack = node.callDecoration("getStack") is_non_printing_mesh = False if per_object_stack: is_non_printing_mesh = any( per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) # Find a reason not to add the node if node.callDecoration( "getBuildPlateNumber" ) != self._build_plate_number and type( node) is not ConvexHullNode: # NB: ConvexHullNodes get none of the usual decorators, so skip checking for them continue if getattr(node, "_outside_buildarea", False) and not is_non_printing_mesh: continue temp_list.append(node) if not is_non_printing_mesh: has_printing_mesh = True Job.yieldThread() #If the list doesn't have any model with suitable settings then clean the list # otherwise CuraEngine will crash if not has_printing_mesh: temp_list.clear() if temp_list: object_groups.append(temp_list) global_stack = CuraApplication.getInstance( ).getGlobalContainerStack() if not global_stack: return extruders_enabled = { position: stack.isEnabled for position, stack in global_stack.extruders.items() } filtered_object_groups = [] has_model_with_disabled_extruders = False associated_disabled_extruders = set() for group in object_groups: stack = global_stack skip_group = False for node in group: # Only check if the printing extruder is enabled for printing meshes is_non_printing_mesh = node.callDecoration( "evaluateIsNonPrintingMesh") extruder_position = node.callDecoration( "getActiveExtruderPosition") if extruder_position is None: # raft meshes may not have an extruder position (yet) extruder_position = "0" if not is_non_printing_mesh and not extruders_enabled[ extruder_position]: skip_group = True has_model_with_disabled_extruders = True associated_disabled_extruders.add(extruder_position) if not skip_group: filtered_object_groups.append(group) if has_model_with_disabled_extruders: self.setResult(StartJobResult.ObjectsWithDisabledExtruder) associated_disabled_extruders = { str(c) for c in sorted( [int(p) + 1 for p in associated_disabled_extruders]) } self.setMessage(", ".join(associated_disabled_extruders)) return # There are cases when there is nothing to slice. This can happen due to one at a time slicing not being # able to find a possible sequence or because there are no objects on the build plate (or they are outside # the build volume) if not filtered_object_groups: self.setResult(StartJobResult.NothingToSlice) return container_registry = ContainerRegistry.getInstance() stack_id = stack.getId() # Adapt layer_height and material_flow for a slanted gantry gantry_angle = self._scene.getRoot().callDecoration( "getGantryAngle") #gantry_angle = float(self._preferences.getValue("BeltPlugin/gantry_angle")) if gantry_angle: # not 0 or None # Act on a copy of the stack, so these changes don't cause a reslice _stack = CuraContainerStack(stack_id + "_temp") for index, container in enumerate(stack.getContainers()): if container_registry.isReadOnly(container.getId()): _stack.replaceContainer(index, container) else: _stack.replaceContainer(index, copy.deepcopy(container)) stack = _stack # Make sure CuraEngine does not create any supports # support_enable is set in the frontend so support options are settable, # but CuraEngine support structures don't work for slanted gantry stack.setProperty("support_enable", "value", False) # Make sure CuraEngine does not create a raft (we create one manually) # Adhesion type is used in the frontend to show the raft in the viewport stack.setProperty("adhesion_type", "value", "none") for key in ["layer_height", "layer_height_0"]: current_value = stack.getProperty(key, "value") stack.setProperty(key, "value", current_value / math.sin(gantry_angle)) self._buildGlobalSettingsMessage(stack) self._buildGlobalInheritsStackMessage(stack) # Build messages for extruder stacks # Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first, # then CuraEngine can slice with the wrong settings. This I think should be fixed in CuraEngine as well. extruder_stack_list = sorted(list(global_stack.extruders.items()), key=lambda item: int(item[0])) for _, extruder_stack in extruder_stack_list: if gantry_angle: # not 0 or None # Act on a copy of the stack, so these changes don't cause a reslice _extruder_stack = CuraContainerStack( extruder_stack.getId() + "_temp") for index, container in enumerate( extruder_stack.getContainers()): if container_registry.isReadOnly(container.getId()): _extruder_stack.replaceContainer(index, container) else: _extruder_stack.replaceContainer( index, copy.deepcopy(container)) extruder_stack = _extruder_stack extruder_stack.setNextStack(stack) for key in [ "material_flow", "prime_tower_flow", "spaghetti_flow" ]: if extruder_stack.hasProperty(key, "value"): current_value = extruder_stack.getProperty( key, "value") extruder_stack.setProperty( key, "value", current_value * math.sin(gantry_angle)) self._buildExtruderMessage(extruder_stack) bottom_cutting_meshes = [] raft_meshes = [] support_meshes = [] if gantry_angle: # not 0 or None for group in filtered_object_groups: added_meshes = [] for object in group: is_non_printing_mesh = False per_object_stack = object.callDecoration("getStack") # ConvexHullNodes get none of the usual decorators. If it made it here, it is meant to be printed if type(object) is ConvexHullNode: raft_thickness = self._preferences.getValue( "BeltPlugin/raft_thickness") raft_margin = self._preferences.getValue( "BeltPlugin/raft_margin") mb = MeshBuilder() hull_polygon = object.getHull() if raft_margin > 0: hull_polygon = hull_polygon.getMinkowskiHull( Polygon.approximatedCircle(raft_margin)) mb.addConvexPolygonExtrusion( hull_polygon.getPoints()[::-1], 0, raft_thickness) new_node = self._addMeshFromBuilder(mb, "raftMesh") added_meshes.append(new_node) raft_meshes.append(new_node.getName()) elif not is_non_printing_mesh: # add support mesh if needed belt_support_gantry_angle_bias = None belt_support_minimum_island_area = None if per_object_stack: is_non_printing_mesh = any( per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) node_enable_support = per_object_stack.getProperty( "support_enable", "value") if per_object_stack.getProperty( "support_mesh", "value"): node_enable_support = node_enable_support or per_object_stack.getProperty( "support_mesh_drop_down", "value") add_support_mesh = node_enable_support if node_enable_support is not None else global_enable_support belt_support_gantry_angle_bias = self._preferences.getValue( "BeltPlugin/support_gantry_angle_bias") belt_support_minimum_island_area = self._preferences.getValue( "BeltPlugin/support_minimum_island_area") else: add_support_mesh = global_enable_support if add_support_mesh: #preferences = Application.getInstance().getPreferences() if belt_support_gantry_angle_bias is None: belt_support_gantry_angle_bias = self._preferences.getValue( "BeltPlugin/support_gantry_angle_bias") biased_down_angle = math.radians( belt_support_gantry_angle_bias) if belt_support_minimum_island_area is None: belt_support_minimum_island_area = self._preferences.getValue( "BeltPlugin/support_minimum_island_area" ) support_mesh_data = SupportMeshCreator( down_vector=numpy.array([ 0, -math.cos( math.radians(biased_down_angle)), -math.sin(biased_down_angle) ]), bottom_cut_off=stack.getProperty( "wall_line_width_0", "value") / 2, minimum_island_area= belt_support_minimum_island_area ).createSupportMeshForNode(object) if support_mesh_data: new_node = self._addMeshFromData( support_mesh_data, "generatedSupportMesh") added_meshes.append(new_node) support_meshes.append(new_node.getName()) # check if the bottom needs to be cut off aabb = object.getBoundingBox() if aabb.bottom < 0: # mesh extends below the belt; add a cutting mesh to cut off the part below the bottom height = -aabb.bottom center = Vector(aabb.center.x, -height / 2, aabb.center.z) mb = MeshBuilder() mb.addCube(width=aabb.width, height=height, depth=aabb.depth, center=center) new_node = self._addMeshFromBuilder( mb, "bottomCuttingMesh") added_meshes.append(new_node) bottom_cutting_meshes.append( new_node.getName()) if added_meshes: group += added_meshes transform_matrix = self._scene.getRoot().callDecoration( "getTransformMatrix") front_offset = None raft_offset = 0 raft_speed = None raft_flow = 1.0 if self._preferences.getValue("BeltPlugin/raft"): raft_offset = self._preferences.getValue( "BeltPlugin/raft_thickness") raft_speed = self._preferences.getValue( "BeltPlugin/raft_speed") raft_flow = self._preferences.getValue( "BeltPlugin/raft_flow") * math.sin(gantry_angle) adhesion_extruder_nr = stack.getProperty("adhesion_extruder_nr", "value") support_extruder_nr = stack.getProperty("support_extruder_nr", "value") for group in filtered_object_groups: group_message = self._slice_message.addRepeatedMessage( "object_lists") if group[0].getParent() is not None and group[0].getParent( ).callDecoration("isGroup"): self._handlePerObjectSettings(group[0].getParent(), group_message) if transform_matrix: scene_front = None for object in group: if type(object) is ConvexHullNode: continue is_non_printing_mesh = object.getName( ) in bottom_cutting_meshes or object.getName( ) in raft_meshes if not is_non_printing_mesh: per_object_stack = object.callDecoration( "getStack") if per_object_stack: is_non_printing_mesh = any( per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) if not is_non_printing_mesh: _front = object.getBoundingBox().back if scene_front is None or _front < scene_front: scene_front = _front if scene_front is not None: front_offset = transformVertices( numpy.array([[0, 0, scene_front]]), transform_matrix)[0][1] for object in group: if type(object) is ConvexHullNode: continue mesh_data = object.getMeshData() rot_scale = object.getWorldTransformation().getTransposed( ).getData()[0:3, 0:3] translate = object.getWorldTransformation().getData()[:3, 3] # offset all non-raft objects if rafts are enabled # air gap is applied here to vertically offset objects from the raft if object.getName() not in raft_meshes: translate[1] += raft_offset if front_offset: translate[2] -= front_offset # This effectively performs a limited form of MeshData.getTransformed that ignores normals. verts = mesh_data.getVertices() verts = verts.dot(rot_scale) verts += translate if transform_matrix: verts = transformVertices(verts, transform_matrix) # Convert from Y up axes to Z up axes. Equals a 90 degree rotation. verts[:, [1, 2]] = verts[:, [2, 1]] verts[:, 1] *= -1 obj = group_message.addRepeatedMessage("objects") obj.id = id(object) obj.name = object.getName() indices = mesh_data.getIndices() if indices is not None: flat_verts = numpy.take(verts, indices.flatten(), axis=0) else: flat_verts = numpy.array(verts) obj.vertices = flat_verts if object.getName() in raft_meshes: self._addSettingsMessage( obj, { "wall_line_count": 99999999, "speed_wall_0": raft_speed, "speed_wall_x": raft_speed, "material_flow": raft_flow, "extruder_nr": adhesion_extruder_nr }) elif object.getName() in support_meshes: self._addSettingsMessage( obj, { "support_mesh": "True", "support_mesh_drop_down": "False", "extruder_nr": support_extruder_nr }) elif object.getName() in bottom_cutting_meshes: self._addSettingsMessage( obj, { "cutting_mesh": True, "wall_line_count": 0, "top_layers": 0, "bottom_layers": 0, "infill_line_distance": 0, "extruder_nr": 0 }) else: self._handlePerObjectSettings(object, obj) Job.yieldThread() # Store the front-most coordinate of the scene so the scene can be moved back into place post slicing # TODO: this should be handled per mesh-group instead of per scene # One-at-a-time printing should be disabled for slanted gantry printers for now self._scene.getRoot().callDecoration("setSceneFrontOffset", front_offset) self.setResult(StartJobResult.Finished)