diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index 2ba0047cd4..d864f4288b 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -51,6 +51,7 @@ class ConvexHullDecorator(SceneNodeDecorator): return None hull = self._compute2DConvexHull() + if self._global_stack and self._node: if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32))) @@ -72,7 +73,9 @@ class ConvexHullDecorator(SceneNodeDecorator): if self._global_stack: if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): - return self._compute2DConvexHeadMin() + head_with_fans = self._compute2DConvexHeadMin() + head_with_fans_with_adhesion_margin = self._add2DAdhesionMargin(head_with_fans) + return head_with_fans_with_adhesion_margin return None ## Get convex hull of the node @@ -84,6 +87,7 @@ class ConvexHullDecorator(SceneNodeDecorator): if self._global_stack: if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"): + # Printing one at a time and it's not an object in a group return self._compute2DConvexHull() return None @@ -99,11 +103,6 @@ class ConvexHullDecorator(SceneNodeDecorator): convex_hull = self.getConvexHull() if self._convex_hull_node: - # Check if convex hull has changed - if (self._convex_hull_node.getHull() == convex_hull and - self._convex_hull_node.getThickness() == self._raft_thickness): - - return self._convex_hull_node.setParent(None) hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, self._raft_thickness, root) self._convex_hull_node = hull_node @@ -219,6 +218,44 @@ class ConvexHullDecorator(SceneNodeDecorator): min_head_hull = self._compute2DConvexHull().getMinkowskiHull(head_and_fans) return min_head_hull + ## Compensate given 2D polygon with adhesion margin + # \return 2D polygon with added margin + 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 _roundHull(self, convex_hull): return convex_hull.getMinkowskiHull(Polygon(numpy.array([[-0.5, -0.5], [-0.5, 0.5], [0.5, 0.5], [0.5, -0.5]], numpy.float32))) @@ -249,4 +286,5 @@ class ConvexHullDecorator(SceneNodeDecorator): _affected_settings = [ "adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", - "raft_surface_thickness", "raft_airgap", "print_sequence"] + "raft_surface_thickness", "raft_airgap", "print_sequence", + "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"] diff --git a/cura/ConvexHullNode.py b/cura/ConvexHullNode.py index f73db2a597..703dfb0bed 100644 --- a/cura/ConvexHullNode.py +++ b/cura/ConvexHullNode.py @@ -28,15 +28,16 @@ class ConvexHullNode(SceneNode): # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting. self._mesh_height = 0.1 + self._thickness = thickness + # The node this mesh is "watching" self._node = node + self._convex_hull_head_mesh = None + self._node.decoratorsChanged.connect(self._onNodeDecoratorsChanged) self._onNodeDecoratorsChanged(self._node) - self._convex_hull_head_mesh = None - self._hull = hull - self._thickness = thickness if self._hull: hull_mesh_builder = MeshBuilder() @@ -46,11 +47,6 @@ class ConvexHullNode(SceneNode): hull_mesh = hull_mesh_builder.build() self.setMeshData(hull_mesh) - convex_hull_head = self._node.callDecoration("getConvexHullHead") - if convex_hull_head: - convex_hull_head_builder = MeshBuilder() - convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height-thickness) - self._convex_hull_head_mesh = convex_hull_head_builder.build() def getHull(self): return self._hull @@ -78,6 +74,12 @@ class ConvexHullNode(SceneNode): def _onNodeDecoratorsChanged(self, node): self._color = Color(35, 35, 35, 0.5) + convex_hull_head = self._node.callDecoration("getConvexHullHead") + if convex_hull_head: + convex_hull_head_builder = MeshBuilder() + convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height-self._thickness) + self._convex_hull_head_mesh = convex_hull_head_builder.build() + if not node: return diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 7f6da8d821..ca09cbc5dc 100644 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -64,9 +64,8 @@ class MachineManager(QObject): # Make sure _active_container_stack is properly initiated ExtruderManager.getInstance().setActiveExtruderIndex(0) - self._auto_change_material_hotend_flood_window = 10 # The minimum number of seconds between asking if the material or hotend on the machine should be used - self._auto_change_material_hotend_flood_time = 0 # The last timestamp (in seconds) when the user was asked about changing the material or hotend to whatis loaded on the machine - self._auto_change_material_hotend_flood_last_choice = None # The last choice that was made, so we can apply that choice again + self._auto_materials_changed = {} + self._auto_hotends_changed = {} globalContainerChanged = pyqtSignal() activeMaterialChanged = pyqtSignal() @@ -104,48 +103,46 @@ class MachineManager(QObject): if not self._global_container_stack: return - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = self._global_container_stack.getBottom().getId(), name = hotend_id) - if containers: + containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type="variant", definition=self._global_container_stack.getBottom().getId(), name=hotend_id) + if containers: # New material ID is known extruder_manager = ExtruderManager.getInstance() - old_index = extruder_manager.activeExtruderIndex - if old_index != index: - extruder_manager.setActiveExtruderIndex(index) - else: - old_index = None + extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId)) + matching_extruder = None + for extruder in extruders: + if str(index) == extruder.getMetaDataEntry("position"): + matching_extruder = extruder + break + if matching_extruder and matching_extruder.findContainer({"type": "variant"}).getName() != hotend_id: + # Save the material that needs to be changed. Multiple changes will be handled by the callback. + self._auto_hotends_changed[str(index)] = containers[0].getId() + Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), + catalog.i18nc("@label", + "Do you want to change the materials and hotends to match the material in your printer?"), + catalog.i18nc("@label", + "The materials and / or hotends on your printer were changed. For best results always slice for the materials . hotends that are inserted in your printer."), + buttons=QMessageBox.Yes + QMessageBox.No, + icon=QMessageBox.Question, + callback=self._materialHotendChangedCallback) - if self.activeVariantId != containers[0].getId(): - if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window: - Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the hotend to match the hotend in your printer?"), - catalog.i18nc("@label", "The hotend on your printer was changed. For best results always slice for the hotend that is inserted in your printer."), - buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._hotendChangedDialogCallback, callback_arguments = [index, containers[0].getId()]) - else: - self._hotendChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId()) - if old_index is not None: - extruder_manager.setActiveExtruderIndex(old_index) else: Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id)) - def _hotendChangedDialogCallback(self, button, index, hotend_id): - self._auto_change_material_hotend_flood_time = time.time() - self._auto_change_material_hotend_flood_last_choice = button - - if button == QMessageBox.No: - return - - Logger.log("d", "Setting hotend variant of hotend %d to %s" % (index, hotend_id)) - + def _autoUpdateHotends(self): extruder_manager = ExtruderManager.getInstance() - old_index = extruder_manager.activeExtruderIndex - if old_index != index: - extruder_manager.setActiveExtruderIndex(index) - else: - old_index = None + for position in self._auto_hotends_changed: + hotend_id = self._auto_hotends_changed[position] + old_index = extruder_manager.activeExtruderIndex - self.setActiveVariant(hotend_id) + if old_index != int(position): + extruder_manager.setActiveExtruderIndex(int(position)) + else: + old_index = None + Logger.log("d", "Setting hotend variant of hotend %s to %s" % (position, hotend_id)) + self.setActiveVariant(hotend_id) - if old_index is not None: - extruder_manager.setActiveExtruderIndex(old_index) + if old_index is not None: + extruder_manager.setActiveExtruderIndex(old_index) def _onMaterialIdChanged(self, index, material_id): if not self._global_container_stack: @@ -154,50 +151,50 @@ class MachineManager(QObject): definition_id = "fdmprinter" if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): definition_id = self._global_container_stack.getBottom().getId() - + extruder_manager = ExtruderManager.getInstance() containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id) - if containers: - extruder_manager = ExtruderManager.getInstance() - old_index = extruder_manager.activeExtruderIndex - if old_index != index: - extruder_manager.setActiveExtruderIndex(index) - else: - old_index = None + if containers: # New material ID is known + extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId)) + matching_extruder = None + for extruder in extruders: + if str(index) == extruder.getMetaDataEntry("position"): + matching_extruder = extruder + break - if self.activeMaterialId != containers[0].getId(): - if time.time() - self._auto_change_material_hotend_flood_time > self._auto_change_material_hotend_flood_window: - Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the material to match the material in your printer?"), - catalog.i18nc("@label", "The material on your printer was changed. For best results always slice for the material that is inserted in your printer."), - buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._materialIdChangedDialogCallback, callback_arguments = [index, containers[0].getId()]) - else: - self._materialIdChangedDialogCallback(self._auto_change_material_hotend_flood_last_choice, index, containers[0].getId()) - if old_index is not None: - extruder_manager.setActiveExtruderIndex(old_index) + if matching_extruder and matching_extruder.findContainer({"type":"material"}).getMetaDataEntry("GUID") != material_id: + # Save the material that needs to be changed. Multiple changes will be handled by the callback. + self._auto_materials_changed[str(index)] = containers[0].getId() + Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the materials and hotends to match the material in your printer?"), + catalog.i18nc("@label", "The materials and / or hotends on your printer were changed. For best results always slice for the materials . hotends that are inserted in your printer."), + buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._materialHotendChangedCallback) else: Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id)) - def _materialIdChangedDialogCallback(self, button, index, material_id): - self._auto_change_material_hotend_flood_time = time.time() - self._auto_change_material_hotend_flood_last_choice = button - + def _materialHotendChangedCallback(self, button): if button == QMessageBox.No: + self._auto_materials_changed = {} + self._auto_hotends_changed = {} return + self._autoUpdateMaterials() + self._autoUpdateHotends() - Logger.log("d", "Setting material of hotend %d to %s" % (index, material_id)) - + def _autoUpdateMaterials(self): extruder_manager = ExtruderManager.getInstance() - old_index = extruder_manager.activeExtruderIndex - if old_index != index: - extruder_manager.setActiveExtruderIndex(index) - else: - old_index = None + for position in self._auto_materials_changed: + material_id = self._auto_materials_changed[position] + old_index = extruder_manager.activeExtruderIndex - self.setActiveMaterial(material_id) + if old_index != int(position): + extruder_manager.setActiveExtruderIndex(int(position)) + else: + old_index = None - if old_index is not None: - extruder_manager.setActiveExtruderIndex(old_index) + Logger.log("d", "Setting material of hotend %s to %s" % (position, material_id)) + self.setActiveMaterial(material_id) + if old_index is not None: + extruder_manager.setActiveExtruderIndex(old_index) def _onGlobalPropertyChanged(self, key, property_name): if property_name == "value": diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 15de585ae0..11c5dc5f84 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -80,6 +80,7 @@ class GCodeWriter(MeshWriter): flat_quality_id = machine_manager.duplicateContainer(container_with_profile.getId()) flat_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = flat_quality_id)[0] + flat_quality._dirty = False user_settings = stack.getTop() # We don't want to send out any signals, so disconnect them. @@ -97,11 +98,12 @@ class GCodeWriter(MeshWriter): flat_extruder_quality_id = machine_manager.duplicateContainer(extruder_quality.getId()) flat_extruder_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=flat_extruder_quality_id)[0] + flat_extruder_quality._dirty = False extruder_user_settings = extruder.getTop() # We don't want to send out any signals, so disconnect them. flat_extruder_quality.propertyChanged.disconnectAll() - + for key in extruder_user_settings.getAllKeys(): flat_extruder_quality.setProperty(key, "value", extruder_user_settings.getProperty(key, "value"))