diff --git a/cura/Arrange.py b/cura/Arrange.py index 0d1f2e0c06..335e42a267 100755 --- a/cura/Arrange.py +++ b/cura/Arrange.py @@ -52,6 +52,8 @@ class Arrange: # Place all objects fixed nodes for fixed_node in fixed_nodes: vertices = fixed_node.callDecoration("getConvexHull") + if not vertices: + continue points = copy.deepcopy(vertices._points) shape_arr = ShapeArray.fromPolygon(points, scale = scale) arranger.place(0, 0, shape_arr) diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index 50fa8ce7f6..fc72ea34de 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -56,6 +56,11 @@ class ConvexHullDecorator(SceneNodeDecorator): 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: diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 5026f9760d..c70d9343cf 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -22,6 +22,14 @@ class SettingOverrideDecorator(SceneNodeDecorator): ## Event indicating that the user selected a different extruder. activeExtruderChanged = Signal() + ## Non-printing meshes + # + # If these settings are True for any mesh, the mesh does not need a convex hull, + # and is sent to the slicer regardless of whether it fits inside the build volume. + # Note that Support Mesh is not in here because it actually generates + # g-code in the volume of the mesh. + _non_printing_mesh_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh"} + def __init__(self): super().__init__() self._stack = PerObjectContainerStack(stack_id = id(self)) @@ -78,6 +86,8 @@ class SettingOverrideDecorator(SceneNodeDecorator): Application.getInstance().getBackend().needsSlicing() Application.getInstance().getBackend().tickle() + self._node._non_printing_mesh = any(self._stack.getProperty(setting, "value") for setting in self._non_printing_mesh_settings) + ## Makes sure that the stack upon which the container stack is placed is # kept up to date. def _updateNextStack(self): diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index b922baea52..caa8c5e569 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -45,14 +45,6 @@ class GcodeStartEndFormatter(Formatter): ## Job class that builds up the message of scene data to send to CuraEngine. class StartSliceJob(Job): - ## Meshes that are sent to the engine regardless of being outside of the - # build volume. - # - # If these settings are True for any mesh, the build volume is ignored. - # Note that Support Mesh is not in here because it actually generates - # g-code in the volume of the mesh. - _not_printed_mesh_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh"} - def __init__(self, slice_message): super().__init__() @@ -141,8 +133,7 @@ class StartSliceJob(Job): temp_list = [] for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: - if not getattr(node, "_outside_buildarea", False)\ - or (node.callDecoration("getStack") and any(node.callDecoration("getStack").getProperty(setting, "value") for setting in self._not_printed_mesh_settings)): + if not getattr(node, "_outside_buildarea", False) or getattr(node, "_non_printing_mesh", False): temp_list.append(node) Job.yieldThread() diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index c15431c9e2..65b67791c7 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -26,17 +26,92 @@ Item { spacing: UM.Theme.getSize("default_margin").height + Row + { + spacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label","Mesh Type") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + height: UM.Theme.getSize("setting").height + verticalAlignment: Text.AlignVCenter + } + + ComboBox + { + id: meshTypeSelection + style: UM.Theme.styles.combobox + onActivated: { + UM.ActiveTool.setProperty("MeshType", model.get(index).type) + } + model: ListModel + { + id: meshTypeModel + Component.onCompleted: + { + meshTypeModel.append({ + type: "", + text: catalog.i18nc("@label", "Normal model") + }); + meshTypeModel.append({ + type: "support_mesh", + text: catalog.i18nc("@label", "Print as support") + }); + meshTypeModel.append({ + type: "anti_overhang_mesh", + text: catalog.i18nc("@label", "Don't support overlap with other models") + }); + meshTypeModel.append({ + type: "cutting_mesh", + text: catalog.i18nc("@label", "Modify settings for overlap with other models") + }); + meshTypeModel.append({ + type: "infill_mesh", + text: catalog.i18nc("@label", "Modify settings for infill of other models") + }); + + meshTypeSelection.updateCurrentIndex(); + } + } + + function updateCurrentIndex() + { + var mesh_type = UM.ActiveTool.properties.getValue("MeshType"); + for(var index=0; index < meshTypeSelection.model.count; index++) + { + if(meshTypeSelection.model.get(index).type == mesh_type) + { + meshTypeSelection.currentIndex = index; + return; + } + } + meshTypeSelection.currentIndex = 0; + } + } + + Connections + { + target: UM.Selection + onSelectionChanged: meshTypeSelection.updateCurrentIndex() + } + + } + Column { // This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar. // It kinda looks ugly otherwise (big panel, no content on it) + id: currentSettings property int maximumHeight: 200 * screenScaleFactor height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("default_lining").height), maximumHeight) + visible: ["support_mesh", "anti_overhang_mesh"].indexOf(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type) == -1 ScrollView { height: parent.height - width: UM.Theme.getSize("setting").width + UM.Theme.getSize("setting").height + width: UM.Theme.getSize("setting").width style: UM.Theme.styles.scrollview ListView @@ -49,6 +124,7 @@ Item { id: addedSettingsModel; containerId: Cura.MachineManager.activeDefinitionId expanded: [ "*" ] + exclude: [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ] visibilityHandler: Cura.PerObjectSettingVisibilityHandler { @@ -58,6 +134,7 @@ Item { delegate: Row { + spacing: - UM.Theme.getSize("default_margin").width Loader { id: settingLoader @@ -112,7 +189,7 @@ Item { Button { - width: (UM.Theme.getSize("setting").height / 2) | 0 + width: Math.floor(UM.Theme.getSize("setting").height / 2) height: UM.Theme.getSize("setting").height onClicked: addedSettingsModel.setVisible(model.key, false) @@ -125,7 +202,7 @@ Item { { anchors.verticalCenter: parent.verticalCenter width: parent.width - height: parent.height / 2 + height: width sourceSize.width: width sourceSize.height: width color: control.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button") @@ -201,9 +278,9 @@ Item { Button { - id: customise_settings_button; - height: UM.Theme.getSize("setting").height; - visible: parseInt(UM.Preferences.getValue("cura/active_mode")) == 1 + id: customiseSettingsButton; + height: UM.Theme.getSize("setting_control").height; + visible: currentSettings.visible text: catalog.i18nc("@action:button", "Select settings"); @@ -223,21 +300,12 @@ Item { { text: control.text; color: UM.Theme.getColor("setting_control_text"); + font: UM.Theme.getFont("default") anchors.centerIn: parent } } onClicked: settingPickDialog.visible = true; - - Connections - { - target: UM.Preferences; - - onPreferenceChanged: - { - customise_settings_button.visible = parseInt(UM.Preferences.getValue("cura/active_mode")) - } - } } } @@ -325,7 +393,7 @@ Item { } visibilityHandler: UM.SettingPreferenceVisibilityHandler {} expanded: [ "*" ] - exclude: [ "machine_settings", "command_line_settings" ] + exclude: [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ] } delegate:Loader { diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index dc6efafa13..d2db5ff420 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -8,6 +8,7 @@ from UM.Application import Application from UM.Preferences import Preferences from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from cura.Settings.ExtruderManager import ExtruderManager +from UM.Settings.SettingInstance import SettingInstance from UM.Event import Event @@ -18,7 +19,7 @@ class PerObjectSettingsTool(Tool): super().__init__() self._model = None - self.setExposedProperties("SelectedObjectId", "ContainerID", "SelectedActiveExtruder") + self.setExposedProperties("SelectedObjectId", "ContainerID", "SelectedActiveExtruder", "MeshType") self._advanced_mode = False self._multi_extrusion = False @@ -70,6 +71,39 @@ class PerObjectSettingsTool(Tool): selected_object.addDecorator(SettingOverrideDecorator()) selected_object.callDecoration("setActiveExtruder", extruder_stack_id) + def setMeshType(self, mesh_type): + selected_object = Selection.getSelectedObject(0) + stack = selected_object.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. + if not stack: + selected_object.addDecorator(SettingOverrideDecorator()) + stack = selected_object.callDecoration("getStack") + + settings = stack.getTop() + for property_key in ["infill_mesh", "cutting_mesh", "support_mesh", "anti_overhang_mesh"]: + if property_key != mesh_type: + if settings.getInstance(property_key): + settings.removeInstance(property_key) + else: + if not (settings.getInstance(property_key) and settings.getProperty(property_key, "value")): + definition = stack.getSettingDefinition(property_key) + new_instance = SettingInstance(definition, settings) + new_instance.setProperty("value", True) + new_instance.resetState() # Ensure that the state is not seen as a user state. + settings.addInstance(new_instance) + + def getMeshType(self): + selected_object = Selection.getSelectedObject(0) + stack = selected_object.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. + if not stack: + return "" + + settings = stack.getTop() + for property_key in ["infill_mesh", "cutting_mesh", "support_mesh", "anti_overhang_mesh"]: + if settings.getInstance(property_key) and settings.getProperty(property_key, "value"): + return property_key + + return "" + def _onPreferenceChanged(self, preference): if preference == "cura/active_mode": self._advanced_mode = Preferences.getInstance().getValue(preference) == 1 diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 90f402b78f..e96c7e9bda 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -27,25 +27,33 @@ class SolidView(View): self._enabled_shader = None self._disabled_shader = None + self._non_printing_shader = None self._extruders_model = ExtrudersModel() + self._theme = None def beginRendering(self): scene = self.getController().getScene() renderer = self.getRenderer() + if not self._theme: + self._theme = Application.getInstance().getTheme() + if not self._enabled_shader: self._enabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader")) - theme = Application.getInstance().getTheme() - self._enabled_shader.setUniformValue("u_overhangColor", Color(*theme.getColor("model_overhang").getRgb())) + self._enabled_shader.setUniformValue("u_overhangColor", Color(*self._theme.getColor("model_overhang").getRgb())) if not self._disabled_shader: self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader")) - theme = Application.getInstance().getTheme() - self._disabled_shader.setUniformValue("u_diffuseColor1", Color(*theme.getColor("model_unslicable").getRgb())) - self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*theme.getColor("model_unslicable_alt").getRgb())) + self._disabled_shader.setUniformValue("u_diffuseColor1", Color(*self._theme.getColor("model_unslicable").getRgb())) + self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*self._theme.getColor("model_unslicable_alt").getRgb())) self._disabled_shader.setUniformValue("u_width", 50.0) + if not self._non_printing_shader: + self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader")) + self._non_printing_shader.setUniformValue("u_diffuseColor", Color(*self._theme.getColor("model_non_printing").getRgb())) + self._non_printing_shader.setUniformValue("u_opacity", 0.6) + global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack: support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") @@ -68,11 +76,19 @@ class SolidView(View): uniforms = {} shade_factor = 1.0 + per_mesh_stack = node.callDecoration("getStack") + # Get color to render this mesh in from ExtrudersModel extruder_index = 0 extruder_id = node.callDecoration("getActiveExtruder") if extruder_id: extruder_index = max(0, self._extruders_model.find("id", extruder_id)) + + # Use the support extruder instead of the active extruder if this is a support_mesh + if per_mesh_stack: + if per_mesh_stack.getProperty("support_mesh", "value"): + extruder_index = int(global_container_stack.getProperty("support_extruder_nr", "value")) + try: material_color = self._extruders_model.getItem(extruder_index)["color"] except KeyError: @@ -94,19 +110,17 @@ class SolidView(View): except ValueError: pass - if hasattr(node, "_outside_buildarea"): - if node._outside_buildarea: - renderer.queueNode(node, shader = self._disabled_shader) + if getattr(node, "_non_printing_mesh", False): + if per_mesh_stack and (per_mesh_stack.getProperty("infill_mesh", "value") or per_mesh_stack.getProperty("cutting_mesh", "value")): + renderer.queueNode(node, shader = self._non_printing_shader, uniforms = uniforms, transparent = True) else: - renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) + renderer.queueNode(node, shader = self._non_printing_shader, transparent = True) + elif getattr(node, "_outside_buildarea", False): + renderer.queueNode(node, shader = self._disabled_shader) else: - renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms) + renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) if node.callDecoration("isGroup") and Selection.isSelected(node): renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop) def endRendering(self): pass - - #def _onPreferenceChanged(self, preference): - #if preference == "view/show_overhang": ## Todo: This a printer only setting. Should be removed from Uranium. - #self._enabled_material = None diff --git a/resources/shaders/transparent_object.shader b/resources/shaders/transparent_object.shader index faa43bb46c..75fe7e4b2c 100644 --- a/resources/shaders/transparent_object.shader +++ b/resources/shaders/transparent_object.shader @@ -111,6 +111,7 @@ u_modelMatrix = model_matrix u_viewProjectionMatrix = view_projection_matrix u_normalMatrix = normal_matrix u_lightPosition = light_0_position +u_diffuseColor = diffuse_color [attributes] a_vertex = vertex diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index edc88f4654..47de3cd382 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -266,6 +266,7 @@ "model_unslicable": [122, 122, 122, 255], "model_unslicable_alt": [172, 172, 127, 255], "model_selection_outline": [12, 169, 227, 255], + "model_non_printing": [122, 122, 122, 255], "xray": [26, 26, 62, 255], "xray_error": [255, 0, 0, 255],