diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py
index d864f4288b..5185579633 100644
--- a/cura/ConvexHullDecorator.py
+++ b/cura/ConvexHullDecorator.py
@@ -30,9 +30,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
def setNode(self, node):
previous_node = self._node
+ # Disconnect from previous node signals
if previous_node is not None and node is not previous_node:
- previous_node.transformationChanged.connect(self._onChanged)
- previous_node.parentChanged.connect(self._onChanged)
+ previous_node.transformationChanged.disconnect(self._onChanged)
+ previous_node.parentChanged.disconnect(self._onChanged)
super().setNode(node)
@@ -286,5 +287,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", "raft_margin", "print_sequence",
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"]
diff --git a/cura/ConvexHullNode.py b/cura/ConvexHullNode.py
index 703dfb0bed..8e5acf9518 100644
--- a/cura/ConvexHullNode.py
+++ b/cura/ConvexHullNode.py
@@ -23,7 +23,7 @@ class ConvexHullNode(SceneNode):
self._original_parent = parent
# Color of the drawn convex hull
- self._color = Color(35, 35, 35, 192)
+ self._color = Color(0.4, 0.4, 0.4, 1.0)
# The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
self._mesh_height = 0.1
@@ -77,7 +77,7 @@ class ConvexHullNode(SceneNode):
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)
+ 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:
diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index a6d0a3b827..53d509b71c 100644
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -98,7 +98,7 @@ class CuraApplication(QtApplication):
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True)
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True)
SettingDefinition.addSupportedProperty("global_inherits_stack", DefinitionPropertyType.Function, default = "-1")
- SettingDefinition.addSettingType("extruder", int, str, Validator)
+ SettingDefinition.addSettingType("extruder", None, str, Validator)
## Add the 4 types of profiles to storage.
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
@@ -128,6 +128,8 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager()
self._machine_manager = None # This is initialized on demand.
+ self._additional_components = {} # Components to add to certain areas in the interface
+
super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType)
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@@ -201,7 +203,7 @@ class CuraApplication(QtApplication):
"dialog_profile_path",
"dialog_material_path"]:
- Preferences.getInstance().addPreference("local_file/%s" % key, "~/")
+ Preferences.getInstance().addPreference("local_file/%s" % key, os.path.expanduser("~/"))
Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
@@ -553,12 +555,18 @@ class CuraApplication(QtApplication):
def deleteSelection(self):
if not self.getController().getToolsEnabled():
return
-
+ removed_group_nodes = []
op = GroupedOperation()
nodes = Selection.getAllSelectedObjects()
for node in nodes:
op.addOperation(RemoveSceneNodeOperation(node))
-
+ group_node = node.getParent()
+ if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
+ remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
+ if len(remaining_nodes_in_group) == 1:
+ removed_group_nodes.append(group_node)
+ op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
+ op.addOperation(RemoveSceneNodeOperation(group_node))
op.push()
pass
@@ -584,8 +592,7 @@ class CuraApplication(QtApplication):
op.push()
if group_node:
if len(group_node.getChildren()) == 1 and group_node.callDecoration("isGroup"):
- group_node.getChildren()[0].translate(group_node.getPosition())
- group_node.getChildren()[0].setParent(group_node.getParent())
+ op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
op = RemoveSceneNodeOperation(group_node)
op.push()
@@ -626,7 +633,23 @@ class CuraApplication(QtApplication):
if node:
op = SetTransformOperation(node, Vector())
op.push()
-
+
+ ## Select all nodes containing mesh data in the scene.
+ @pyqtSlot()
+ def selectAll(self):
+ if not self.getController().getToolsEnabled():
+ return
+
+ Selection.clear()
+ for node in DepthFirstIterator(self.getController().getScene().getRoot()):
+ if type(node) is not SceneNode:
+ continue
+ if not node.getMeshData() and not node.callDecoration("isGroup"):
+ continue # Node that doesnt have a mesh and is not a group.
+ if node.getParent() and node.getParent().callDecoration("isGroup"):
+ continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
+ Selection.add(node)
+
## Delete all nodes containing mesh data in the scene.
@pyqtSlot()
def deleteAll(self):
@@ -650,6 +673,7 @@ class CuraApplication(QtApplication):
op.addOperation(RemoveSceneNodeOperation(node))
op.push()
+ Selection.clear()
## Reset all translation on nodes with mesh data.
@pyqtSlot()
@@ -876,4 +900,22 @@ class CuraApplication(QtApplication):
self.getMainWindow().setMinimumSize(size)
def getBuildVolume(self):
- return self._volume
\ No newline at end of file
+ return self._volume
+
+ additionalComponentsChanged = pyqtSignal(str, arguments = ["areaId"])
+
+ @pyqtProperty("QVariantMap", notify = additionalComponentsChanged)
+ def additionalComponents(self):
+ return self._additional_components
+
+ ## Add a component to a list of components to be reparented to another area in the GUI.
+ # The actual reparenting is done by the area itself.
+ # \param area_id \type{str} Identifying name of the area to which the component should be reparented
+ # \param component \type{QQuickComponent} The component that should be reparented
+ @pyqtSlot(str, "QVariant")
+ def addAdditionalComponent(self, area_id, component):
+ if area_id not in self._additional_components:
+ self._additional_components[area_id] = []
+ self._additional_components[area_id].append(component)
+
+ self.additionalComponentsChanged.emit(area_id)
diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py
index b50bb95e7f..6061a2e49b 100644
--- a/cura/MachineActionManager.py
+++ b/cura/MachineActionManager.py
@@ -57,9 +57,10 @@ class MachineActionManager(QObject):
def addRequiredAction(self, definition_id, action_key):
if action_key in self._machine_actions:
if definition_id in self._required_actions:
- self._required_actions[definition_id] |= {self._machine_actions[action_key]}
+ if self._machine_actions[action_key] not in self._required_actions[definition_id]:
+ self._required_actions[definition_id].append(self._machine_actions[action_key])
else:
- self._required_actions[definition_id] = {self._machine_actions[action_key]}
+ self._required_actions[definition_id] = [self._machine_actions[action_key]]
else:
raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id))
@@ -67,9 +68,10 @@ class MachineActionManager(QObject):
def addSupportedAction(self, definition_id, action_key):
if action_key in self._machine_actions:
if definition_id in self._supported_actions:
- self._supported_actions[definition_id] |= {self._machine_actions[action_key]}
+ if self._machine_actions[action_key] not in self._supported_actions[definition_id]:
+ self._supported_actions[definition_id].append(self._machine_actions[action_key])
else:
- self._supported_actions[definition_id] = {self._machine_actions[action_key]}
+ self._supported_actions[definition_id] = [self._machine_actions[action_key]]
else:
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py
index d2a848dd72..56daaddc18 100644
--- a/cura/PlatformPhysics.py
+++ b/cura/PlatformPhysics.py
@@ -71,7 +71,6 @@ class PlatformPhysics:
# If there is no convex hull for the node, start calculating it and continue.
if not node.getDecorator(ConvexHullDecorator):
node.addDecorator(ConvexHullDecorator())
- node.callDecoration("recomputeConvexHull")
if Preferences.getInstance().getValue("physics/automatic_push_free"):
# Check for collisions between convex hulls
@@ -95,11 +94,11 @@ class PlatformPhysics:
# Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
head_hull = node.callDecoration("getConvexHullHead")
if head_hull:
- overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull"))
+ overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHullHead"))
if not overlap:
other_head_hull = other_node.callDecoration("getConvexHullHead")
if other_head_hull:
- overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull)
+ overlap = node.callDecoration("getConvexHullHead").intersectsPolygon(other_head_hull)
else:
own_convex_hull = node.callDecoration("getConvexHull")
other_convex_hull = other_node.callDecoration("getConvexHull")
diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py
index 9184db109a..82be7c480f 100644
--- a/cura/Settings/ContainerManager.py
+++ b/cura/Settings/ContainerManager.py
@@ -244,6 +244,7 @@ class ContainerManager(QObject):
if not type_name or entry["type"] == type_name:
filters.append(filter_string)
+ filters.append("All Files (*)")
return filters
## Export a container to a file
@@ -280,6 +281,9 @@ class ContainerManager(QObject):
return { "status": "error", "message": "Container not found"}
container = containers[0]
+ if UM.Platform.isOSX() and "." in file_url:
+ file_url = file_url[:file_url.rfind(".")]
+
for suffix in mime_type.suffixes:
if file_url.endswith(suffix):
break
@@ -301,7 +305,7 @@ class ContainerManager(QObject):
with UM.SaveFile(file_url, "w") as f:
f.write(contents)
- return { "status": "success", "message": "Succesfully exported container"}
+ return { "status": "success", "message": "Succesfully exported container", "path": file_url}
## Imports a profile from a file
#
@@ -371,11 +375,20 @@ class ContainerManager(QObject):
"container": container_type
}
- suffix_list = "*." + mime_type.preferredSuffix
+ suffix = mime_type.preferredSuffix
+ if UM.Platform.isOSX() and "." in suffix:
+ # OSX's File dialog is stupid and does not allow selecting files with a . in its name
+ suffix = suffix[suffix.index(".") + 1:]
+
+ suffix_list = "*." + suffix
for suffix in mime_type.suffixes:
if suffix == mime_type.preferredSuffix:
continue
+ if UM.Platform.isOSX() and "." in suffix:
+ # OSX's File dialog is stupid and does not allow selecting files with a . in its name
+ suffix = suffix[suffix.index("."):]
+
suffix_list += ", *." + suffix
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py
index ca09cbc5dc..9e09ad83be 100644
--- a/cura/Settings/MachineManager.py
+++ b/cura/Settings/MachineManager.py
@@ -26,7 +26,7 @@ class MachineManager(QObject):
self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
- self._global_stack_valid = None
+ self._active_stack_valid = None
self._onGlobalContainerChanged()
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
@@ -74,7 +74,7 @@ class MachineManager(QObject):
activeStackChanged = pyqtSignal()
globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed.
- globalValidationChanged = pyqtSignal() # Emitted whenever a validation inside global container is changed
+ activeValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
@@ -269,25 +269,29 @@ class MachineManager(QObject):
if property_name == "global_inherits_stack":
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
# Update the global user value when the "global_inherits_stack" function points to a different stack
- stack_index = int(self._active_container_stack.getProperty(key, property_name))
- extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
+ extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack"))
+ if target_stack_position == -1: # Prevent -1 from selecting wrong stack.
+ target_stack = self._active_container_stack
+ else:
+ target_stack = extruder_stacks[target_stack_position]
- if len(extruder_stacks) > stack_index:
- new_value = extruder_stacks[stack_index].getProperty(key, "value")
- if self._global_container_stack.getProperty(key, "value") != new_value:
- self._global_container_stack.getTop().setProperty(key, "value", new_value)
+ new_value = target_stack.getProperty(key, "value")
+ if self._global_container_stack.getProperty(key, "value") != new_value:
+ self._global_container_stack.getTop().setProperty(key, "value", new_value)
if property_name == "validationState":
- if self._global_stack_valid:
+ if self._active_stack_valid:
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
- self._global_stack_valid = False
- self.globalValidationChanged.emit()
+ self._active_stack_valid = False
+ self.activeValidationChanged.emit()
else:
has_errors = self._checkStackForErrors(self._active_container_stack)
if not has_errors:
- self._global_stack_valid = True
- self.globalValidationChanged.emit()
+ self._active_stack_valid = True
+ self.activeValidationChanged.emit()
+
def _onGlobalContainerChanged(self):
if self._global_container_stack:
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
@@ -310,8 +314,6 @@ class MachineManager(QObject):
self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
- self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack)
- self.globalValidationChanged.emit()
material = self._global_container_stack.findContainer({"type": "material"})
material.nameChanged.connect(self._onMaterialNameChanged)
@@ -329,6 +331,8 @@ class MachineManager(QObject):
self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
else:
self._active_container_stack = self._global_container_stack
+ self._active_stack_valid = not self._checkStackForErrors(self._active_container_stack)
+ self.activeValidationChanged.emit()
def _onInstanceContainersChanged(self, container):
container_type = container.getMetaDataEntry("type")
@@ -433,11 +437,11 @@ class MachineManager(QObject):
return len(user_settings) != 0
## Check if the global profile does not contain error states
- # Note that the _global_stack_valid is cached due to performance issues
+ # Note that the _active_stack_valid is cached due to performance issues
# Calling _checkStackForErrors on every change is simply too expensive
- @pyqtProperty(bool, notify = globalValidationChanged)
- def isGlobalStackValid(self):
- return bool(self._global_stack_valid)
+ @pyqtProperty(bool, notify = activeValidationChanged)
+ def isActiveStackValid(self):
+ return bool(self._active_stack_valid)
@pyqtProperty(str, notify = activeStackChanged)
def activeUserProfileId(self):
@@ -549,7 +553,7 @@ class MachineManager(QObject):
return ""
@pyqtSlot(str, str)
- def renameQualityContainer(self, container_id, new_name):
+ def renameQualityContainer(self, container_id, nbalew_name):
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality")
if containers:
new_name = self._createUniqueName("quality", containers[0].getName(), new_name,
diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py
index 24360ed992..f1e34a939a 100644
--- a/cura/Settings/SettingOverrideDecorator.py
+++ b/cura/Settings/SettingOverrideDecorator.py
@@ -75,6 +75,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# \param extruder_stack_id The new extruder stack to print with.
def setActiveExtruder(self, extruder_stack_id):
self._extruder_stack = extruder_stack_id
+ self._updateNextStack()
self.activeExtruderChanged.emit()
def getStack(self):
diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py
index 57d76b2783..ea3ca30118 100644
--- a/plugins/3MFReader/ThreeMFReader.py
+++ b/plugins/3MFReader/ThreeMFReader.py
@@ -9,18 +9,20 @@ from UM.Math.Vector import Vector
from UM.Scene.SceneNode import SceneNode
from UM.Scene.GroupDecorator import GroupDecorator
from UM.Math.Quaternion import Quaternion
-
from UM.Job import Job
import math
import zipfile
-import xml.etree.ElementTree as ET
+try:
+ import xml.etree.cElementTree as ET
+except ImportError:
+ import xml.etree.ElementTree as ET
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
class ThreeMFReader(MeshReader):
def __init__(self):
- super(ThreeMFReader, self).__init__()
+ super().__init__()
self._supported_extensions = [".3mf"]
self._namespaces = {
@@ -116,4 +118,10 @@ class ThreeMFReader(MeshReader):
except Exception as e:
Logger.log("e", "exception occured in 3mf reader: %s", e)
- return result
+ try: # Selftest - There might be more functions that should fail
+ boundingBox = result.getBoundingBox()
+ boundingBox.isValid()
+ except:
+ return None
+
+ return result
diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto
index 0c4803cc19..289c0a98a0 100644
--- a/plugins/CuraEngineBackend/Cura.proto
+++ b/plugins/CuraEngineBackend/Cura.proto
@@ -13,6 +13,7 @@ message Slice
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
SettingList global_settings = 2; // The global settings used for the whole print job
repeated Extruder extruders = 3; // The settings sent to each extruder object
+ repeated SettingExtruder global_inherits_stack = 4; //From which stack the setting would inherit if not defined in a stack.
}
message Extruder
@@ -108,8 +109,14 @@ message Setting {
bytes value = 2; // The value of the setting
}
+message SettingExtruder {
+ string name = 1; //The setting key.
+
+ int32 extruder = 2; //From which extruder stack the setting should inherit.
+}
+
message GCodePrefix {
- bytes data = 2; // Header string to be prenpended before the rest of the gcode sent from the engine
+ bytes data = 2; //Header string to be prepended before the rest of the g-code sent from the engine.
}
message SlicingFinished {
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index aedc91f130..a822512218 100644
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -13,9 +13,11 @@ from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform
+
import cura.Settings
from cura.OneAtATimeIterator import OneAtATimeIterator
+from cura.Settings.ExtruderManager import ExtruderManager
from . import ProcessSlicedLayersJob
from . import ProcessGCodeJob
from . import StartSliceJob
@@ -40,7 +42,8 @@ class CuraEngineBackend(Backend):
def __init__(self):
super().__init__()
- # Find out where the engine is located, and how it is called. This depends on how Cura is packaged and which OS we are running on.
+ # Find out where the engine is located, and how it is called.
+ # This depends on how Cura is packaged and which OS we are running on.
default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", "CuraEngine")
if hasattr(sys, "frozen"):
default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "CuraEngine")
@@ -59,7 +62,7 @@ class CuraEngineBackend(Backend):
self._stored_layer_data = []
self._stored_optimized_layer_data = []
- #Triggers for when to (re)start slicing:
+ # Triggers for when to (re)start slicing:
self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
@@ -68,15 +71,15 @@ class CuraEngineBackend(Backend):
cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
self._onActiveExtruderChanged()
- #When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
- #This timer will group them up, and only slice for the last setting changed signal.
- #TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
+ # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
+ # This timer will group them up, and only slice for the last setting changed signal.
+ # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
self._change_timer = QTimer()
self._change_timer.setInterval(500)
self._change_timer.setSingleShot(True)
self._change_timer.timeout.connect(self.slice)
- #Listeners for receiving messages from the back-end.
+ # Listeners for receiving messages from the back-end.
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage
self._message_handlers["cura.proto.Progress"] = self._onProgressMessage
@@ -86,19 +89,19 @@ class CuraEngineBackend(Backend):
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
self._start_slice_job = None
- self._slicing = False #Are we currently slicing?
- self._restart = False #Back-end is currently restarting?
- self._enabled = True #Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around.
- self._always_restart = True #Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
- self._process_layers_job = None #The currently active job to process layers, or None if it is not processing layers.
+ self._slicing = False # Are we currently slicing?
+ self._restart = False # Back-end is currently restarting?
+ self._enabled = True # Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around.
+ self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
+ self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
- self._backend_log_max_lines = 200 # Maximal count of lines to buffer
- self._error_message = None #Pop-up message that shows errors.
+ self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
+ self._error_message = None # Pop-up message that shows errors.
self.backendQuit.connect(self._onBackendQuit)
self.backendConnected.connect(self._onBackendConnected)
- #When a tool operation is in progress, don't slice. So we need to listen for tool operations.
+ # When a tool operation is in progress, don't slice. So we need to listen for tool operations.
Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted)
Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped)
@@ -117,9 +120,10 @@ class CuraEngineBackend(Backend):
# \return list of commands and args / parameters.
def getEngineCommand(self):
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
- return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, "-vv"]
+ return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
- ## Emitted when we get a message containing print duration and material amount. This also implies the slicing has finished.
+ ## Emitted when we get a message containing print duration and material amount.
+ # This also implies the slicing has finished.
# \param time The amount of time the print will take.
# \param material_amount The amount of material the print will use.
printDurationMessage = Signal()
@@ -133,7 +137,7 @@ class CuraEngineBackend(Backend):
## Perform a slice of the scene.
def slice(self):
self._slice_start_time = time()
- if not self._enabled or not self._global_container_stack: #We shouldn't be slicing.
+ if not self._enabled or not self._global_container_stack: # We shouldn't be slicing.
# try again in a short time
self._change_timer.start()
return
@@ -143,10 +147,10 @@ class CuraEngineBackend(Backend):
self._stored_layer_data = []
self._stored_optimized_layer_data = []
- if self._slicing: #We were already slicing. Stop the old job.
+ if self._slicing: # We were already slicing. Stop the old job.
self._terminate()
- if self._process_layers_job: #We were processing layers. Stop that, the layers are going to change soon.
+ if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon.
self._process_layers_job.abort()
self._process_layers_job = None
@@ -183,7 +187,7 @@ class CuraEngineBackend(Backend):
self._process.terminate()
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait())
self._process = None
- except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
+ except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
## Event handler to call when the job to initiate the slicing process is
@@ -204,7 +208,7 @@ class CuraEngineBackend(Backend):
if job.getResult() == StartSliceJob.StartJobResult.SettingError:
if Application.getInstance().getPlatformActivity:
- self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."), lifetime = 10)
+ self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
else:
@@ -213,7 +217,7 @@ class CuraEngineBackend(Backend):
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
if Application.getInstance().getPlatformActivity:
- self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found."), lifetime = 10)
+ self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found."))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
else:
@@ -252,6 +256,9 @@ class CuraEngineBackend(Backend):
return
super()._onSocketError(error)
+ if error.getErrorCode() == Arcus.ErrorCode.Debug:
+ return
+
self._terminate()
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
@@ -262,7 +269,7 @@ class CuraEngineBackend(Backend):
# \param instance The setting instance that has changed.
# \param property The property of the setting instance that has changed.
def _onSettingChanged(self, instance, property):
- if property == "value": #Only reslice if the value has changed.
+ if property == "value": # Only reslice if the value has changed.
self._onChanged()
## Called when a sliced layer data message is received from the engine.
@@ -313,7 +320,7 @@ class CuraEngineBackend(Backend):
## Called when a print time message is received from the engine.
#
- # \param message The protobuf message containing the print time and
+ # \param message The protobuff message containing the print time and
# material amount per extruder
def _onPrintTimeMaterialEstimates(self, message):
material_amounts = []
@@ -348,8 +355,8 @@ class CuraEngineBackend(Backend):
#
# \param tool The tool that the user is using.
def _onToolOperationStarted(self, tool):
- self._terminate() # Do not continue slicing once a tool has started
- self._enabled = False # Do not reslice when a tool is doing it's 'thing'
+ self._terminate() # Do not continue slicing once a tool has started
+ self._enabled = False # Do not reslice when a tool is doing it's 'thing'
## Called when the user stops using some tool.
#
@@ -357,13 +364,13 @@ class CuraEngineBackend(Backend):
#
# \param tool The tool that the user was using.
def _onToolOperationStopped(self, tool):
- self._enabled = True # Tool stop, start listening for changes again.
+ self._enabled = True # Tool stop, start listening for changes again.
## Called when the user changes the active view mode.
def _onActiveViewChanged(self):
if Application.getInstance().getController().getActiveView():
view = Application.getInstance().getController().getActiveView()
- if view.getPluginId() == "LayerView": #If switching to layer view, we should process the layers if that hasn't been done yet.
+ if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True
# There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
@@ -388,22 +395,35 @@ class CuraEngineBackend(Backend):
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack.containersChanged.disconnect(self._onChanged)
+ extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ if extruders:
+ for extruder in extruders:
+ extruder.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
if self._global_container_stack:
- self._global_container_stack.propertyChanged.connect(self._onSettingChanged) #Note: Only starts slicing when the value changed.
+ self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
self._global_container_stack.containersChanged.connect(self._onChanged)
+ extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ if extruders:
+ for extruder in extruders:
+ extruder.propertyChanged.connect(self._onSettingChanged)
self._onActiveExtruderChanged()
self._onChanged()
def _onActiveExtruderChanged(self):
+ if self._global_container_stack:
+ # Connect all extruders of the active machine. This might cause a few connects that have already happend,
+ # but that shouldn't cause issues as only new / unique connections are added.
+ extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
+ if extruders:
+ for extruder in extruders:
+ extruder.propertyChanged.connect(self._onSettingChanged)
if self._active_extruder_stack:
- self._active_extruder_stack.propertyChanged.disconnect(self._onSettingChanged)
self._active_extruder_stack.containersChanged.disconnect(self._onChanged)
self._active_extruder_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack()
if self._active_extruder_stack:
- self._active_extruder_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
self._active_extruder_stack.containersChanged.connect(self._onChanged)
diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
index c2f73cf5b7..7443340c5b 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
@@ -56,10 +56,9 @@ class ProcessSlicedLayersJob(Job):
## Remove old layer data (if any)
for node in DepthFirstIterator(self._scene.getRoot()):
- if type(node) is SceneNode and node.getMeshData():
- if node.callDecoration("getLayerData"):
- self._scene.getRoot().removeChild(node)
- Job.yieldThread()
+ if node.callDecoration("getLayerData"):
+ node.getParent().removeChild(node)
+ break
if self._abort_requested:
if self._progress:
self._progress.hide()
@@ -74,7 +73,7 @@ class ProcessSlicedLayersJob(Job):
# instead simply offset all other layers so the lowest layer is always 0.
min_layer_number = 0
for layer in self._layers:
- if(layer.id < min_layer_number):
+ if layer.id < min_layer_number:
min_layer_number = layer.id
current_layer = 0
@@ -98,7 +97,7 @@ class ProcessSlicedLayersJob(Job):
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
if polygon.point_type == 0: # Point2D
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
- else: # Point3D
+ else: # Point3D
points = points.reshape((-1,3))
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
@@ -108,15 +107,14 @@ class ProcessSlicedLayersJob(Job):
# This uses manual array creation + copy rather than numpy.insert since this is
# faster.
new_points = numpy.empty((len(points), 3), numpy.float32)
- if polygon.point_type == 0: # Point2D
- new_points[:,0] = points[:,0]
- new_points[:,1] = layer.height/1000 # layer height value is in backend representation
- new_points[:,2] = -points[:,1]
+ if polygon.point_type == 0: # Point2D
+ new_points[:, 0] = points[:, 0]
+ new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation
+ new_points[:, 2] = -points[:, 1]
else: # Point3D
- new_points[:,0] = points[:,0]
- new_points[:,1] = points[:,2]
- new_points[:,2] = -points[:,1]
-
+ new_points[:, 0] = points[:, 0]
+ new_points[:, 1] = points[:, 2]
+ new_points[:, 2] = -points[:, 1]
this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths)
this_poly.buildCache()
@@ -185,4 +183,3 @@ class ProcessSlicedLayersJob(Job):
else:
if self._progress:
self._progress.hide()
-
diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py
index a726e239e0..5b948c90ab 100644
--- a/plugins/CuraEngineBackend/StartSliceJob.py
+++ b/plugins/CuraEngineBackend/StartSliceJob.py
@@ -13,6 +13,7 @@ from UM.Scene.SceneNode import SceneNode
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Validator import ValidatorState
+from UM.Settings.SettingRelation import RelationType
from cura.OneAtATimeIterator import OneAtATimeIterator
@@ -24,6 +25,7 @@ class StartJobResult(IntEnum):
SettingError = 3
NothingToSlice = 4
+
## Formatter class that handles token expansion in start/end gcod
class GcodeStartEndFormatter(Formatter):
def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class]
@@ -37,6 +39,7 @@ class GcodeStartEndFormatter(Formatter):
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", key)
return "{" + str(key) + "}"
+
## Job class that builds up the message of scene data to send to CuraEngine.
class StartSliceJob(Job):
def __init__(self, slice_message):
@@ -71,7 +74,7 @@ class StartSliceJob(Job):
return
# Don't slice if there is a setting with an error value.
- if self._checkStackForErrors(stack):
+ if not Application.getInstance().getMachineManager().isActiveStackValid:
self.setResult(StartJobResult.SettingError)
return
@@ -123,11 +126,15 @@ class StartSliceJob(Job):
if temp_list:
object_groups.append(temp_list)
+ # 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 object_groups:
self.setResult(StartJobResult.NothingToSlice)
return
self._buildGlobalSettingsMessage(stack)
+ self._buildGlobalInheritsStackMessage(stack)
for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack)
@@ -171,6 +178,7 @@ class StartSliceJob(Job):
Logger.logException("w", "Unable to do token replacement on start/end gcode")
return str(value).encode("utf-8")
+ ## Create extruder message from stack
def _buildExtruderMessage(self, stack):
message = self._slice_message.addRepeatedMessage("extruders")
message.id = int(stack.getMetaDataEntry("position"))
@@ -209,11 +217,54 @@ class StartSliceJob(Job):
else:
setting_message.value = str(value).encode("utf-8")
+ ## Sends for some settings which extruder they should fallback to if not
+ # set.
+ #
+ # This is only set for settings that have the global_inherits_stack
+ # property.
+ #
+ # \param stack The global stack with all settings, from which to read the
+ # global_inherits_stack property.
+ def _buildGlobalInheritsStackMessage(self, stack):
+ for key in stack.getAllKeys():
+ extruder = int(stack.getProperty(key, "global_inherits_stack"))
+ if extruder >= 0: #Set to a specific extruder.
+ setting_extruder = self._slice_message.addRepeatedMessage("global_inherits_stack")
+ setting_extruder.name = key
+ setting_extruder.extruder = extruder
+
+ ## Check if a node has per object settings and ensure that they are set correctly in the message
+ # \param node \type{SceneNode} Node to check.
+ # \param message object_lists message to put the per object settings in
def _handlePerObjectSettings(self, node, message):
stack = node.callDecoration("getStack")
+ # Check if the node has a stack attached to it and the stack has any settings in the top container.
if stack:
- for key in stack.getAllKeys():
+ # Check all settings for relations, so we can also calculate the correct values for dependant settings.
+ changed_setting_keys = set(stack.getTop().getAllKeys())
+ for key in stack.getTop().getAllKeys():
+ instance = stack.getTop().getInstance(key)
+ self._addRelations(changed_setting_keys, instance.definition.relations)
+ Job.yieldThread()
+
+ # Ensure that the engine is aware what the build extruder is
+ if stack.getProperty("machine_extruder_count", "value") > 1:
+ changed_setting_keys.add("extruder_nr")
+
+ # Get values for all changed settings
+ for key in changed_setting_keys:
setting = message.addRepeatedMessage("settings")
setting.name = key
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
Job.yieldThread()
+
+ ## Recursive function to put all settings that require eachother for value changes in a list
+ # \param relations_set \type{set} Set of keys (strings) of settings that are influenced
+ # \param relations list of relation objects that need to be checked.
+ def _addRelations(self, relations_set, relations):
+ for relation in filter(lambda r: r.role == "value", relations):
+ if relation.type == RelationType.RequiresTarget:
+ continue
+
+ relations_set.add(relation.target.key)
+ self._addRelations(relations_set, relation.target.relations)
\ No newline at end of file
diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py
index 11c5dc5f84..8e3be15ffe 100644
--- a/plugins/GCodeWriter/GCodeWriter.py
+++ b/plugins/GCodeWriter/GCodeWriter.py
@@ -9,6 +9,8 @@ import UM.Settings.ContainerRegistry
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
+from UM.Settings.InstanceContainer import InstanceContainer
+
import re #For escaping characters in the settings.
import json
@@ -61,6 +63,20 @@ class GCodeWriter(MeshWriter):
return False
+ ## Create a new container with container 2 as base and container 1 written over it.
+ def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
+ flat_container = InstanceContainer(instance_container2.getName())
+ flat_container.setDefinition(instance_container2.getDefinition())
+ flat_container.setMetaData(instance_container2.getMetaData())
+
+ for key in instance_container2.getAllKeys():
+ flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value"))
+
+ for key in instance_container1.getAllKeys():
+ flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value"))
+ return flat_container
+
+
## Serialises a container stack to prepare it for writing at the end of the
# g-code.
#
@@ -74,38 +90,13 @@ class GCodeWriter(MeshWriter):
prefix_length = len(prefix)
container_with_profile = stack.findContainer({"type": "quality"})
- machine_manager = CuraApplication.getInstance().getMachineManager()
-
- # Duplicate the current quality profile and update it with any user settings.
- 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.
- flat_quality.propertyChanged.disconnectAll()
-
- for key in user_settings.getAllKeys():
- flat_quality.setProperty(key, "value", user_settings.getProperty(key, "value"))
-
- serialized = flat_quality.serialize()
-
+ flat_global_container = self._createFlattenedContainerInstance(stack.getTop(),container_with_profile)
+ serialized = flat_global_container.serialize()
data = {"global_quality": serialized}
- manager = ExtruderManager.getInstance()
- for extruder in manager.getMachineExtruders(stack.getId()):
+
+ for extruder in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
extruder_quality = extruder.findContainer({"type": "quality"})
-
- 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"))
+ flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized)
diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py
index 87e1a30834..f60d492ec7 100644
--- a/plugins/LayerView/LayerView.py
+++ b/plugins/LayerView/LayerView.py
@@ -11,6 +11,7 @@ from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Job import Job
from UM.Preferences import Preferences
+from UM.Logger import Logger
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
@@ -34,7 +35,7 @@ class LayerView(View):
self._shader = None
self._selection_shader = None
self._num_layers = 0
- self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100)
+ self._layer_percentage = 0 # what percentage of layers need to be shown (Slider gives value between 0 - 100)
self._proxy = LayerViewProxy.LayerViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._max_layers = 0
@@ -43,12 +44,14 @@ class LayerView(View):
self._current_layer_jumps = None
self._top_layers_job = None
self._activity = False
+ self._old_max_layers = 0
Preferences.getInstance().addPreference("view/top_layer_count", 5)
+ Preferences.getInstance().addPreference("view/only_show_top_layers", False)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
-
+ self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
self._busy = False
def getActivity(self):
@@ -100,7 +103,7 @@ class LayerView(View):
continue
# Render all layers below a certain number as line mesh instead of vertices.
- if self._current_layer_num - self._solid_layers > -1:
+ if self._current_layer_num - self._solid_layers > -1 and not self._only_show_top_layers:
start = 0
end = 0
element_counts = layer_data.getElementCounts()
@@ -126,14 +129,12 @@ class LayerView(View):
if self._current_layer_num > self._max_layers:
self._current_layer_num = self._max_layers
- self.resetLayerData()
self._startUpdateTopLayers()
self.currentLayerNumChanged.emit()
def calculateMaxLayers(self):
scene = self.getController().getScene()
- renderer = self.getRenderer() # TODO: @UnusedVariable
self._activity = True
self._old_max_layers = self._max_layers
@@ -199,7 +200,7 @@ class LayerView(View):
if not job.getResult():
return
-
+ self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data.
self._current_layer_mesh = job.getResult().get("layers")
self._current_layer_jumps = job.getResult().get("jumps")
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
@@ -207,14 +208,15 @@ class LayerView(View):
self._top_layers_job = None
def _onPreferencesChanged(self, preference):
- if preference != "view/top_layer_count":
+ if preference != "view/top_layer_count" and preference != "view/only_show_top_layers":
return
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
+ self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
- self.resetLayerData()
self._startUpdateTopLayers()
+
class _CreateTopLayersJob(Job):
def __init__(self, scene, layer_number, solid_layers):
super().__init__()
@@ -242,20 +244,20 @@ class _CreateTopLayersJob(Job):
try:
layer = layer_data.getLayer(layer_number).createMesh()
- except Exception as e:
- print(e)
+ except Exception:
+ Logger.logException("w", "An exception occurred while creating layer mesh.")
return
if not layer or layer.getVertices() is None:
continue
- layer_mesh.addIndices(layer_mesh._vertex_count+layer.getIndices())
+ layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices())
layer_mesh.addVertices(layer.getVertices())
# Scale layer color by a brightness factor based on the current layer number
# This will result in a range of 0.5 - 1.0 to multiply colors by.
- brightness = numpy.ones((1,4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
- brightness[0, 3] = 1.0;
+ brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
+ brightness[0, 3] = 1.0
layer_mesh.addColors(layer.getColors() * brightness)
if self._cancel:
@@ -271,7 +273,7 @@ class _CreateTopLayersJob(Job):
if not jump_mesh or jump_mesh.getVertices() is None:
jump_mesh = None
- self.setResult({ "layers": layer_mesh.build(), "jumps": jump_mesh })
+ self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
def cancel(self):
self._cancel = True
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
index 79f10f508f..482fb73b0f 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
@@ -47,7 +47,9 @@ Item {
id: extruders_model
onRowsInserted: extruderSelector.visible = extruders_model.rowCount() > 1
onModelReset: extruderSelector.visible = extruders_model.rowCount() > 1
+ onModelChanged: extruderSelector.color = extruders_model.getItem(extruderSelector.currentIndex).colour
}
+ property string color: extruders_model.getItem(extruderSelector.currentIndex).colour
visible: extruders_model.rowCount() > 1
textRole: "name"
width: UM.Theme.getSize("setting_control").width
@@ -88,7 +90,7 @@ Item {
anchors.leftMargin: UM.Theme.getSize("default_lining").width
anchors.verticalCenter: parent.verticalCenter
- color: extruders_model.getItem(extruderSelector.currentIndex).colour
+ color: extruderSelector.color
border.width: UM.Theme.getSize("default_lining").width
border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border")
}
@@ -125,7 +127,11 @@ Item {
}
}
}
-
+ Component.onCompleted:
+ {
+ // Ensure primary extruder is set as active
+ UM.ActiveTool.setProperty("SelectedActiveExtruder", extruders_model.getItem(0).id)
+ }
onActivated: UM.ActiveTool.setProperty("SelectedActiveExtruder", extruders_model.getItem(index).id);
onModelChanged: updateCurrentIndex();
@@ -136,6 +142,7 @@ Item {
if(extruders_model.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder"))
{
extruderSelector.currentIndex = i;
+ extruderSelector.color = extruders_model.getItem(i).colour;
return;
}
}
@@ -243,6 +250,7 @@ Item {
key: model.key
watchedProperties: [ "value", "enabled", "validationState" ]
storeIndex: 0
+ removeUnusedValue: false
}
}
}
diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py
index 2182a0748a..0329fb53e2 100644
--- a/plugins/SolidView/SolidView.py
+++ b/plugins/SolidView/SolidView.py
@@ -3,6 +3,7 @@
from UM.View.View import View
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
+from UM.Scene.Selection import Selection
from UM.Resources import Resources
from UM.Application import Application
from UM.Preferences import Preferences
@@ -57,25 +58,28 @@ class SolidView(View):
# renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(),mode = Renderer.RenderLines)
uniforms = {}
- if self._extruders_model.rowCount() > 0:
+ if self._extruders_model.rowCount() == 0:
+ material = Application.getInstance().getGlobalContainerStack().findContainer({ "type": "material" })
+ material_color = material.getMetaDataEntry("color_code", default = self._extruders_model.defaultColours[0]) if material else self._extruders_model.defaultColours[0]
+ else:
# 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))
- extruder_color = self._extruders_model.getItem(extruder_index)["colour"]
- try:
- # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
- # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
- uniforms["diffuse_color"] = [
- int(extruder_color[1:3], 16) / 255,
- int(extruder_color[3:5], 16) / 255,
- int(extruder_color[5:7], 16) / 255,
- 1.0
- ]
- except ValueError:
- pass
+ material_color = self._extruders_model.getItem(extruder_index)["colour"]
+ try:
+ # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
+ # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
+ uniforms["diffuse_color"] = [
+ int(material_color[1:3], 16) / 255,
+ int(material_color[3:5], 16) / 255,
+ int(material_color[5:7], 16) / 255,
+ 1.0
+ ]
+ except ValueError:
+ pass
if hasattr(node, "_outside_buildarea"):
if node._outside_buildarea:
@@ -84,7 +88,7 @@ class SolidView(View):
renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms)
else:
renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms)
- if node.callDecoration("isGroup"):
+ if node.callDecoration("isGroup") and Selection.isSelected(node):
renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = Renderer.RenderLines)
def endRendering(self):
diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.py b/plugins/UltimakerMachineActions/BedLevelMachineAction.py
index 7dad841340..12df7d6843 100644
--- a/plugins/UltimakerMachineActions/BedLevelMachineAction.py
+++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.py
@@ -18,6 +18,11 @@ class BedLevelMachineAction(MachineAction):
pass
def _reset(self):
+ self._bed_level_position = 0
+ pass
+
+ @pyqtSlot()
+ def startBedLeveling(self):
self._bed_level_position = 0
printer_output_devices = self._getPrinterOutputDevices()
if printer_output_devices:
@@ -52,4 +57,5 @@ class BedLevelMachineAction(MachineAction):
output_device.moveHead(0, 0, -3)
self._bed_level_position += 1
elif self._bed_level_position >= 3:
+ output_device.sendCommand("M18") # Turn off all motors so the user can move the axes
self.setFinished()
\ No newline at end of file
diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.qml b/plugins/UltimakerMachineActions/BedLevelMachineAction.qml
index d043c20df5..c7c4074120 100644
--- a/plugins/UltimakerMachineActions/BedLevelMachineAction.qml
+++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.qml
@@ -47,37 +47,37 @@ Cura.MachineAction
text: catalog.i18nc("@label", "For every position; insert a piece of paper under the nozzle and adjust the print bed height. The print bed height is right when the paper is slightly gripped by the tip of the nozzle.")
}
- Item
+ Row
{
id: bedlevelingWrapper
anchors.top: bedlevelingText.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
- height: skipBedlevelingButton.height
- width: bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height < bedLevelMachineAction.width ? bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height : bedLevelMachineAction.width
+ width: childrenRect.width
+ spacing: UM.Theme.getSize("default_margin").width
+
Button
{
- id: bedlevelingButton
- anchors.top: parent.top
- anchors.left: parent.left
- text: catalog.i18nc("@action:button","Move to Next Position");
+ id: startBedLevelingButton
+ text: catalog.i18nc("@action:button","Start Bed Leveling")
onClicked:
{
- manager.moveToNextLevelPosition()
+ startBedLevelingButton.visible = false;
+ bedlevelingButton.visible = true;
+ checkupMachineAction.heatupHotendStarted = false;
+ checkupMachineAction.heatupBedStarted = false;
+ manager.startCheck();
}
}
Button
{
- id: skipBedlevelingButton
- anchors.top: parent.width < bedLevelMachineAction.width ? parent.top : bedlevelingButton.bottom
- anchors.topMargin: parent.width < bedLevelMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2
- anchors.left: parent.width < bedLevelMachineAction.width ? bedlevelingButton.right : parent.left
- anchors.leftMargin: parent.width < bedLevelMachineAction.width ? UM.Theme.getSize("default_margin").width : 0
- text: catalog.i18nc("@action:button","Skip bed leveling");
+ id: bedlevelingButton
+ text: catalog.i18nc("@action:button","Move to Next Position")
+ visible: false
onClicked:
{
- manager.setFinished()
+ manager.moveToNextLevelPosition();
}
}
}
diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py
index b46c92a82c..355b4dd0c1 100644
--- a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py
+++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py
@@ -155,6 +155,7 @@ class UMOCheckupMachineAction(MachineAction):
if output_devices:
self._output_device = output_devices[0]
try:
+ self._output_device.sendCommand("M18") # Turn off all motors so the user can move the axes
self._output_device.startPollEndstop()
self._output_device.bedTemperatureChanged.connect(self.bedTemperatureChanged)
self._output_device.hotendTemperaturesChanged.connect(self.hotendTemperatureChanged)
diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
index 14ed1e2c51..0bdbc27527 100644
--- a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
+++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml
@@ -39,38 +39,26 @@ Cura.MachineAction
text: catalog.i18nc("@label", "It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional");
}
- Item
+ Row
{
id: startStopButtons
anchors.top: pageDescription.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
- height: childrenRect.height
- width: startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height < checkupMachineAction.width ? startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height : checkupMachineAction.width
+ width: childrenRect.width
+ spacing: UM.Theme.getSize("default_margin").width
Button
{
id: startCheckButton
- anchors.top: parent.top
- anchors.left: parent.left
text: catalog.i18nc("@action:button","Start Printer Check");
onClicked:
{
- checkupMachineAction.heatupHotendStarted = false
- checkupMachineAction.heatupBedStarted = false
- manager.startCheck()
+ checkupMachineAction.heatupHotendStarted = false;
+ checkupMachineAction.heatupBedStarted = false;
+ manager.startCheck();
+ startCheckButton.visible = false;
}
}
-
- Button
- {
- id: skipCheckButton
- anchors.top: parent.width < checkupMachineAction.width ? parent.top : startCheckButton.bottom
- anchors.topMargin: parent.width < checkupMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2
- anchors.left: parent.width < checkupMachineAction.width ? startCheckButton.right : parent.left
- anchors.leftMargin: parent.width < checkupMachineAction.width ? UM.Theme.getSize("default_margin").width : 0
- text: catalog.i18nc("@action:button", "Skip Printer Check");
- onClicked: manager.setFinished()
- }
}
Item
diff --git a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py
index 2e13ec50d5..5d00fd5e3a 100644
--- a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py
+++ b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py
@@ -12,6 +12,9 @@ class UMOUpgradeSelection(MachineAction):
super().__init__("UMOUpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
self._qml_url = "UMOUpgradeSelectionMachineAction.qml"
+ def _reset(self):
+ self.heatedBedChanged.emit()
+
heatedBedChanged = pyqtSignal()
@pyqtProperty(bool, notify = heatedBedChanged)
diff --git a/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml b/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml
index d960bcf976..234e53c0a6 100644
--- a/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml
+++ b/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml
@@ -44,7 +44,7 @@ Cura.MachineAction
text: catalog.i18nc("@label", "Heated bed (official kit or self-built)")
checked: manager.hasHeatedBed
- onClicked: manager.hasHeatedBed ? manager.removeHeatedBed() : manager.addHeatedBed()
+ onClicked: checked ? manager.addHeatedBed() : manager.removeHeatedBed()
}
UM.I18nCatalog { id: catalog; name: "cura"; }
diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
index 37e4eae2d3..0c9b80c010 100644
--- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
+++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml
@@ -56,29 +56,21 @@ Cura.MachineAction
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Cura requires these new features and thus your firmware will most likely need to be upgraded. You can do so now.");
}
- Item
+ Row
{
anchors.top: upgradeText2.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
- width: upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height < upgradeFirmwareMachineAction.width ? upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height : upgradeFirmwareMachineAction.width
+ width: childrenRect.width
+ spacing: UM.Theme.getSize("default_margin").width
Button
{
id: upgradeButton
- anchors.top: parent.top
- anchors.left: parent.left
text: catalog.i18nc("@action:button","Upgrade to Marlin Firmware");
- onClicked: Cura.USBPrinterManager.updateAllFirmware()
- }
- Button
- {
- id: skipUpgradeButton
- anchors.top: parent.width < upgradeFirmwareMachineAction.width ? parent.top : upgradeButton.bottom
- anchors.topMargin: parent.width < upgradeFirmwareMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height / 2
- anchors.left: parent.width < upgradeFirmwareMachineAction.width ? upgradeButton.right : parent.left
- anchors.leftMargin: parent.width < upgradeFirmwareMachineAction.width ? UM.Theme.getSize("default_margin").width : 0
- text: catalog.i18nc("@action:button", "Skip Upgrade");
- onClicked: manager.setFinished()
+ onClicked:
+ {
+ Cura.USBPrinterManager.updateAllFirmware()
+ }
}
}
}
diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json
index 950c856a6e..e7445b6061 100644
--- a/resources/definitions/fdmprinter.def.json
+++ b/resources/definitions/fdmprinter.def.json
@@ -150,6 +150,8 @@
"label": "Number extruders",
"description": "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle.",
"default_value": 1,
+ "minimum_value": "1",
+ "maximum_value": "16",
"type": "int",
"settable_per_mesh": false,
"settable_per_extruder": false,
@@ -630,9 +632,8 @@
"type": "float",
"enabled": "support_enable",
"value": "line_width",
- "global_inherits_stack": "support_extruder_nr",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"support_roof_line_width":
{
@@ -645,9 +646,8 @@
"type": "float",
"enabled": "support_roof_enable",
"value": "line_width",
- "global_inherits_stack": "support_extruder_nr",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"prime_tower_line_width":
{
@@ -1238,30 +1238,28 @@
"default_value": false,
"enabled": "retraction_enable",
"settable_per_mesh": false,
- "settable_per_extruder": true,
- "children": {
- "retraction_hop_only_when_collides": {
- "label": "Z Hop Only Over Printed Parts",
- "description": "Only perform a Z Hop when moving over printed parts which cannot be avoided by horizontal motion by Avoid Printed Parts when Traveling.",
- "type": "bool",
- "default_value": false,
- "enabled": "retraction_enable and retraction_hop_enabled and travel_avoid_other_parts",
- "settable_per_mesh": false,
- "settable_per_extruder": true
- },
- "retraction_hop": {
- "label": "Z Hop Height",
- "description": "The height difference when performing a Z Hop.",
- "unit": "mm",
- "type": "float",
- "default_value": 1,
- "minimum_value_warning": "-0.0001",
- "maximum_value_warning": "10",
- "enabled": "retraction_enable and retraction_hop_enabled",
- "settable_per_mesh": false,
- "settable_per_extruder": true
- }
- }
+ "settable_per_extruder": true
+ },
+ "retraction_hop_only_when_collides": {
+ "label": "Z Hop Only Over Printed Parts",
+ "description": "Only perform a Z Hop when moving over printed parts which cannot be avoided by horizontal motion by Avoid Printed Parts when Traveling.",
+ "type": "bool",
+ "default_value": false,
+ "enabled": "retraction_enable and retraction_hop_enabled and travel_avoid_other_parts",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
+ },
+ "retraction_hop": {
+ "label": "Z Hop Height",
+ "description": "The height difference when performing a Z Hop.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 1,
+ "minimum_value_warning": "-0.0001",
+ "maximum_value_warning": "10",
+ "enabled": "retraction_enable and retraction_hop_enabled",
+ "settable_per_mesh": false,
+ "settable_per_extruder": true
},
"material_standby_temperature":
{
@@ -1444,10 +1442,9 @@
"maximum_value_warning": "150",
"default_value": 60,
"value": "speed_print",
- "global_inherits_stack": "support_extruder_nr",
"enabled": "support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false,
+ "settable_per_extruder": true,
"children":
{
"speed_support_infill":
@@ -1461,10 +1458,9 @@
"maximum_value": "299792458000",
"maximum_value_warning": "150",
"value": "speed_support",
- "global_inherits_stack": "support_extruder_nr",
"enabled": "support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"speed_support_roof":
{
@@ -1478,9 +1474,8 @@
"maximum_value_warning": "150",
"enabled": "support_roof_enable and support_enable",
"value": "speed_support / 1.5",
- "global_inherits_stack": "support_extruder_nr",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
}
}
},
@@ -1516,14 +1511,44 @@
},
"speed_layer_0": {
"label": "Initial Layer Speed",
- "description": "The print speed for the initial layer. A lower value is advised to improve adhesion to the build plate.",
+ "description": "The speed for the initial layer. A lower value is advised to improve adhesion to the build plate.",
"unit": "mm/s",
"type": "float",
"default_value": 30,
"minimum_value": "0.1",
"maximum_value": "299792458000",
"maximum_value_warning": "300",
- "settable_per_mesh": true
+ "settable_per_mesh": true,
+ "children":
+ {
+ "speed_print_layer_0":
+ {
+ "label": "Initial Layer Print Speed",
+ "description": "The speed of printing for the initial layer. A lower value is advised to improve adhesion to the build plate.",
+ "unit": "mm/s",
+ "type": "float",
+ "default_value": 30,
+ "value": "speed_layer_0",
+ "minimum_value": "0.1",
+ "maximum_value": "299792458000",
+ "maximum_value_warning": "300",
+ "settable_per_mesh": true
+ },
+ "speed_travel_layer_0":
+ {
+ "label": "Initial Layer Travel Speed",
+ "description": "The speed of travel moves in the initial layer. A lower value is advised to prevent pulling previously printed parts away from the build plate.",
+ "unit": "mm/s",
+ "type": "float",
+ "default_value": 60,
+ "value": "speed_layer_0 * speed_travel / speed_print",
+ "minimum_value": "0.1",
+ "maximum_value": "299792458000",
+ "maximum_value_warning": "300",
+ "settable_per_mesh": true,
+ "settable_per_extruder": true
+ }
+ }
},
"skirt_brim_speed": {
"label": "Skirt/Brim Speed",
@@ -1551,8 +1576,8 @@
"settable_per_mesh": false,
"settable_per_extruder": false
},
-
-
+
+
"acceleration_enabled": {
"label": "Enable Acceleration Control",
"description": "Enables adjusting the print head acceleration. Increasing the accelerations can reduce printing time at the cost of print quality.",
@@ -1650,10 +1675,9 @@
"maximum_value_warning": "10000",
"default_value": 3000,
"value": "acceleration_print",
- "global_inherits_stack": "support_extruder_nr",
"enabled": "acceleration_enabled and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false,
+ "settable_per_extruder": true,
"children": {
"acceleration_support_infill": {
"label": "Support Infill Acceleration",
@@ -1662,13 +1686,12 @@
"type": "float",
"default_value": 3000,
"value": "acceleration_support",
- "global_inherits_stack": "support_extruder_nr",
"minimum_value": "0.1",
"minimum_value_warning": "100",
"maximum_value_warning": "10000",
"enabled": "acceleration_enabled and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"acceleration_support_roof": {
"label": "Support Roof Acceleration",
@@ -1677,13 +1700,12 @@
"type": "float",
"default_value": 3000,
"value": "acceleration_support",
- "global_inherits_stack": "support_extruder_nr",
"minimum_value": "0.1",
"minimum_value_warning": "100",
"maximum_value_warning": "10000",
"enabled": "acceleration_enabled and support_roof_enable and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
}
}
},
@@ -1742,8 +1764,8 @@
"settable_per_mesh": false
},
-
-
+
+
"jerk_enabled": {
"label": "Enable Jerk Control",
"description": "Enables adjusting the jerk of print head when the velocity in the X or Y axis changes. Increasing the jerk can reduce printing time at the cost of print quality.",
@@ -1841,10 +1863,9 @@
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_print",
- "global_inherits_stack": "support_extruder_nr",
"enabled": "jerk_enabled and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false,
+ "settable_per_extruder": true,
"children": {
"jerk_support_infill": {
"label": "Support Infill Jerk",
@@ -1853,13 +1874,12 @@
"type": "float",
"default_value": 20,
"value": "jerk_support",
- "global_inherits_stack": "support_extruder_nr",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "jerk_enabled and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"jerk_support_roof": {
"label": "Support Roof Jerk",
@@ -1868,13 +1888,12 @@
"type": "float",
"default_value": 20,
"value": "jerk_support",
- "global_inherits_stack": "support_extruder_nr",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "jerk_enabled and support_roof_enable and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
}
}
},
@@ -2180,7 +2199,7 @@
"default_value": "zigzag",
"enabled": "support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"support_connect_zigzags":
{
@@ -2190,7 +2209,7 @@
"default_value": true,
"enabled": "support_enable and (support_pattern == \"zigzag\")",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"support_infill_rate":
{
@@ -2203,7 +2222,7 @@
"default_value": 15,
"enabled": "support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false,
+ "settable_per_extruder": true,
"children": {
"support_line_distance":
{
@@ -2216,7 +2235,7 @@
"enabled": "support_enable",
"value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == \"grid\" else (3 if support_pattern == \"triangles\" else 1))",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
}
}
},
@@ -2389,7 +2408,7 @@
"maximum_value_warning": "100",
"enabled":"support_roof_enable and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false,
+ "settable_per_extruder": true,
"children":
{
"support_roof_line_distance":
@@ -2403,7 +2422,7 @@
"value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == \"grid\" else (3 if support_roof_pattern == \"triangles\" else 1))",
"enabled": "support_roof_enable and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
}
}
},
@@ -2423,7 +2442,7 @@
"default_value": "concentric",
"enabled": "support_roof_enable and support_enable",
"settable_per_mesh": false,
- "settable_per_extruder": false
+ "settable_per_extruder": true
},
"support_use_towers":
{
@@ -3357,9 +3376,9 @@
"type": "float",
"minimum_value": "0",
"maximum_value_warning": "9999",
- "default_value": 0,
- "value": "9999 if draft_shield_height_limitation == 'full' and draft_shield_enabled else 0.0",
- "enabled": "draft_shield_height_limitation == \"limited\"",
+ "default_value": 10,
+ "value": "10",
+ "enabled": "draft_shield_enabled and draft_shield_height_limitation == \"limited\"",
"settable_per_mesh": false,
"settable_per_extruder": false
},
diff --git a/resources/definitions/printrbot_simple.def.json b/resources/definitions/printrbot_simple.def.json
index 0116ba6244..a1963fe20e 100644
--- a/resources/definitions/printrbot_simple.def.json
+++ b/resources/definitions/printrbot_simple.def.json
@@ -7,6 +7,7 @@
"visible": true,
"author": "Calvindog717",
"manufacturer": "PrintrBot",
+ "category": "Other",
"file_formats": "text/x-gcode"
},
diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json
index 3712963f23..d8e73a7e14 100644
--- a/resources/definitions/ultimaker2.def.json
+++ b/resources/definitions/ultimaker2.def.json
@@ -57,7 +57,7 @@
"default_value": 2
},
"gantry_height": {
- "default_value": 55
+ "default_value": 48
},
"machine_use_extruder_offset_to_offset_coords": {
"default_value": true
diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json
index d2174ece80..0815aeee02 100644
--- a/resources/definitions/ultimaker_original.def.json
+++ b/resources/definitions/ultimaker_original.def.json
@@ -15,7 +15,7 @@
"preferred_material": "*pla*",
"preferred_quality": "*normal*",
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
- "supported_actions": ["UMOCheckup", "UpgradeFirmware", "BedLevel", "UMOUpgradeSelection"]
+ "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
},
"overrides": {
diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml
index 67bc5fe149..ed88433e33 100644
--- a/resources/qml/Actions.qml
+++ b/resources/qml/Actions.qml
@@ -27,6 +27,7 @@ Item
property alias multiplyObject: multiplyObjectAction;
+ property alias selectAll: selectAllAction;
property alias deleteAll: deleteAllAction;
property alias reloadAll: reloadAllAction;
property alias resetAllTranslation: resetAllTranslationAction;
@@ -119,7 +120,7 @@ Item
Action
{
id: updateProfileAction;
- enabled: Cura.MachineManager.isGlobalStackValid && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
+ enabled: Cura.MachineManager.isActiveStackValid && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings");
onTriggered: Cura.MachineManager.updateQualityContainerFromUserContainer()
}
@@ -135,7 +136,7 @@ Item
Action
{
id: addProfileAction;
- enabled: Cura.MachineManager.isGlobalStackValid && Cura.MachineManager.hasUserSettings
+ enabled: Cura.MachineManager.isActiveStackValid && Cura.MachineManager.hasUserSettings
text: catalog.i18nc("@action:inmenu menubar:profile","&Create profile from current settings...");
}
@@ -230,6 +231,16 @@ Item
iconName: "edit-duplicate"
}
+ Action
+ {
+ id: selectAllAction;
+ text: catalog.i18nc("@action:inmenu menubar:edit","&Select All Objects");
+ enabled: UM.Controller.toolsEnabled;
+ iconName: "edit-select-all";
+ shortcut: "Ctrl+A";
+ onTriggered: Printer.selectAll();
+ }
+
Action
{
id: deleteAllAction;
diff --git a/resources/qml/AddMachineDialog.qml b/resources/qml/AddMachineDialog.qml
index 38221030ea..350435d1ca 100644
--- a/resources/qml/AddMachineDialog.qml
+++ b/resources/qml/AddMachineDialog.qml
@@ -16,12 +16,23 @@ UM.Dialog
{
id: base
title: catalog.i18nc("@title:window", "Add Printer")
- property string activeManufacturer: "Ultimaker";
+ property string preferredCategory: "Ultimaker"
+ property string activeCategory: preferredCategory
+
+ onVisibilityChanged:
+ {
+ // Reset selection and machine name
+ if (visible) {
+ activeCategory = preferredCategory;
+ machineList.currentIndex = 0;
+ machineName.text = getMachineName();
+ }
+ }
signal machineAdded(string id)
function getMachineName()
{
- var name = machineList.model.getItem(machineList.currentIndex).name
+ var name = machineList.model.get(machineList.currentIndex).name
return name
}
@@ -36,16 +47,32 @@ UM.Dialog
right: parent.right;
bottom: parent.bottom;
}
+
ListView
{
id: machineList
- model: UM.DefinitionContainersModel
+ model: ListModel
{
- id: machineDefinitionsModel
- filter: {"visible":true}
+ id: sortedMachineDefinitionsModel
+ Component.onCompleted: {
+ // DefinitionContainersModel is sorted alphabetically, but we want the preferred
+ // category on top so we create a custom-sorted ListModel from it.
+ var items = [];
+ for(var i in machineDefinitionsModel.items) {
+ var item = machineDefinitionsModel.getItem(i);
+ if (item["category"] == preferredCategory)
+ sortedMachineDefinitionsModel.append(item);
+ else
+ items.push(item);
+ }
+ for(var i in items) {
+ sortedMachineDefinitionsModel.append(items[i]);
+ }
+ }
}
- section.property: "manufacturer"
+
+ section.property: "category"
section.delegate: Button
{
text: section
@@ -76,16 +103,25 @@ UM.Dialog
sourceSize.width: width
sourceSize.height: width
color: palette.windowText
- source: base.activeManufacturer == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right")
+ source: base.activeCategory == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right")
}
}
}
onClicked:
{
- base.activeManufacturer = section;
- machineList.currentIndex = machineList.model.find("manufacturer", section)
- machineName.text = getMachineName()
+ base.activeCategory = section;
+ if (machineList.model.get(machineList.currentIndex).category != section) {
+ // Find the first machine from this category
+ for(var i = 0; i < sortedMachineDefinitionsModel.count; i++) {
+ var item = sortedMachineDefinitionsModel.get(i);
+ if (item.category == section) {
+ machineList.currentIndex = i;
+ break;
+ }
+ }
+ }
+ machineName.text = getMachineName();
}
}
@@ -114,7 +150,7 @@ UM.Dialog
states: State
{
name: "collapsed";
- when: base.activeManufacturer != model.manufacturer;
+ when: base.activeCategory != model.category;
PropertyChanges { target: machineButton; opacity: 0; height: 0; }
}
@@ -161,7 +197,7 @@ UM.Dialog
onClicked:
{
base.visible = false
- var item = machineList.model.getItem(machineList.currentIndex);
+ var item = machineList.model.get(machineList.currentIndex);
Cura.MachineManager.addMachine(machineName.text, item.id)
base.machineAdded(item.id) // Emit signal that the user added a machine.
}
@@ -174,6 +210,11 @@ UM.Dialog
id: catalog;
name: "cura";
}
+ UM.DefinitionContainersModel
+ {
+ id: machineDefinitionsModel
+ filter: { "visible": true }
+ }
SystemPalette { id: palette }
ExclusiveGroup { id: printerGroup; }
}
diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml
index d0870991d2..ae72353e0e 100644
--- a/resources/qml/Cura.qml
+++ b/resources/qml/Cura.qml
@@ -54,10 +54,7 @@ UM.MainWindow
Keys.onPressed: {
if (event.key == Qt.Key_Backspace)
{
- if(objectContextMenu.objectId != 0)
- {
- Printer.deleteObject(objectContextMenu.objectId);
- }
+ Cura.Actions.deleteSelection.trigger()
}
}
@@ -121,6 +118,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.undo; }
MenuItem { action: Cura.Actions.redo; }
MenuSeparator { }
+ MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.deleteSelection; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
@@ -540,6 +538,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.deleteObject; }
MenuItem { action: Cura.Actions.multiplyObject; }
MenuSeparator { }
+ MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
@@ -592,6 +591,7 @@ UM.MainWindow
Menu
{
id: contextMenu;
+ MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
diff --git a/resources/qml/MonitorButton.qml b/resources/qml/MonitorButton.qml
index af163a39e2..4a33b347d3 100644
--- a/resources/qml/MonitorButton.qml
+++ b/resources/qml/MonitorButton.qml
@@ -22,7 +22,7 @@ Rectangle
{
if(!printerConnected)
return UM.Theme.getColor("status_offline")
- else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print")
+ else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print" || Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup" )
return UM.Theme.getColor("status_busy")
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "ready" || Cura.MachineManager.printerOutputDevices[0].jobState == "")
return UM.Theme.getColor("status_ready")
@@ -30,6 +30,8 @@ Rectangle
return UM.Theme.getColor("status_paused")
else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error")
return UM.Theme.getColor("status_stopped")
+ else if (Cura.MachineManager.printerOutputDevices[0].jobState == "offline")
+ return UM.Theme.getColor("status_offline")
else
return UM.Theme.getColor("text")
}
@@ -41,7 +43,10 @@ Rectangle
{
if(!printerConnected)
{
- return catalog.i18nc("@label:", "Please check your printer connections")
+ return catalog.i18nc("@label:", "Not connected to a printer")
+ } else if(Cura.MachineManager.printerOutputDevices[0].jobState == "offline")
+ {
+ return catalog.i18nc("@label:", "Lost connection with the printer")
} else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing")
{
return catalog.i18nc("@label:", "Printing...")
@@ -53,6 +58,10 @@ Rectangle
{
return catalog.i18nc("@label:", "Preparing...")
}
+ else if(Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup")
+ {
+ return catalog.i18nc("@label:", "Waiting for cleanup...")
+ }
else
{
return " "
diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml
index 7e5b4efadc..223b7eda85 100644
--- a/resources/qml/Preferences/GeneralPage.qml
+++ b/resources/qml/Preferences/GeneralPage.qml
@@ -192,6 +192,7 @@ UM.PreferencesPage
}
}
+
UM.TooltipArea {
width: childrenRect.width;
height: childrenRect.height;
@@ -215,6 +216,19 @@ UM.PreferencesPage
}
}
}
+ UM.TooltipArea {
+ width: childrenRect.width
+ height: childrenRect.height
+ text: catalog.i18nc("@info:tooltip", "Should only the top layers be displayed in layerview?")
+
+ CheckBox
+ {
+ id: topLayersOnlyCheckbox
+ text: catalog.i18nc("@option:check", "Only display top layer(s) in layer view")
+ checked: boolCheck(UM.Preferences.getValue("view/only_show_top_layers"))
+ onCheckedChanged: UM.Preferences.setValue("view/only_show_top_layers", checked)
+ }
+ }
Item
{
diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml
index 0dc3bb32eb..b057a33d52 100644
--- a/resources/qml/Preferences/MachinesPage.qml
+++ b/resources/qml/Preferences/MachinesPage.qml
@@ -107,6 +107,12 @@ UM.ManagementPage
contents = content;
content.onCompleted.connect(hide)
}
+ rightButtons: Button
+ {
+ text: catalog.i18nc("@action:button", "Close")
+ iconName: "dialog-close"
+ onClicked: actionDialog.accept()
+ }
}
Row
diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml
index aaaa4f5e9d..f4a8df1dcf 100644
--- a/resources/qml/Preferences/MaterialsPage.qml
+++ b/resources/qml/Preferences/MaterialsPage.qml
@@ -67,6 +67,8 @@ UM.ManagementPage
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId
onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id)
},
+ /*
+ // disabled because it has a lot of issues
Button
{
text: catalog.i18nc("@action:button", "Duplicate");
@@ -89,7 +91,7 @@ UM.ManagementPage
Cura.MachineManager.setActiveMaterial(material_id)
}
- },
+ }, */
Button
{
text: catalog.i18nc("@action:button", "Remove");
@@ -253,7 +255,7 @@ UM.ManagementPage
else if(result.status == "success")
{
messageDialog.icon = StandardIcon.Information
- messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to %1").arg(fileUrl)
+ messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to %1").arg(result.path)
messageDialog.open()
}
CuraApplication.setDefaultPath("dialog_material_path", folder)
diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml
index 8b95de15ee..9ea6e181bb 100644
--- a/resources/qml/SaveButton.qml
+++ b/resources/qml/SaveButton.qml
@@ -84,6 +84,27 @@ Rectangle {
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
+ Row {
+ id: additionalComponentsRow
+ anchors.top: parent.top
+ anchors.right: saveToButton.visible ? saveToButton.left : parent.right
+ anchors.rightMargin: UM.Theme.getSize("default_margin").width
+
+ spacing: UM.Theme.getSize("default_margin").width
+ }
+
+ Connections {
+ target: Printer
+ onAdditionalComponentsChanged:
+ {
+ if(areaId == "saveButton") {
+ for (var component in Printer.additionalComponents["saveButton"]) {
+ Printer.additionalComponents["saveButton"][component].parent = additionalComponentsRow
+ }
+ }
+ }
+ }
+
Button {
id: saveToButton
@@ -102,8 +123,7 @@ Rectangle {
}
style: ButtonStyle {
- background:
- Rectangle
+ background: Rectangle
{
border.width: UM.Theme.getSize("default_lining").width
border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
@@ -126,7 +146,7 @@ Rectangle {
text: control.text;
}
}
- label: Item { }
+ label: Item { }
}
}
diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml
index 5308e45f5a..283e309e6a 100644
--- a/resources/qml/Settings/SettingComboBox.qml
+++ b/resources/qml/Settings/SettingComboBox.qml
@@ -86,18 +86,18 @@ SettingItem
}
}
- onActivated: { forceActiveFocus(); provider.setPropertyValue("value", definition.options[index].key) }
+ onActivated: { forceActiveFocus(); propertyProvider.setPropertyValue("value", definition.options[index].key) }
onModelChanged: updateCurrentIndex();
Connections
{
- target: provider
+ target: propertyProvider
onPropertiesChanged: control.updateCurrentIndex()
}
function updateCurrentIndex() {
for(var i = 0; i < definition.options.length; ++i) {
- if(definition.options[i].key == provider.properties.value) {
+ if(definition.options[i].key == propertyProvider.properties.value) {
currentIndex = i;
return;
}
diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml
index 72c5299b15..d88fc9c457 100644
--- a/resources/qml/Settings/SettingExtruder.qml
+++ b/resources/qml/Settings/SettingExtruder.qml
@@ -105,13 +105,13 @@ SettingItem
onActivated:
{
forceActiveFocus();
- provider.setPropertyValue("value", extruders_model.getItem(index).index)
+ propertyProvider.setPropertyValue("value", extruders_model.getItem(index).index)
}
onModelChanged: updateCurrentIndex();
Connections
{
- target: provider
+ target: propertyProvider
onPropertiesChanged: control.updateCurrentIndex();
}
@@ -119,7 +119,7 @@ SettingItem
{
for(var i = 0; i < extruders_model.rowCount(); ++i)
{
- if(extruders_model.getItem(i).index == provider.properties.value)
+ if(extruders_model.getItem(i).index == propertyProvider.properties.value)
{
currentIndex = i;
return;
diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml
index eb6c37f73c..dae9953690 100644
--- a/resources/qml/Settings/SettingItem.qml
+++ b/resources/qml/Settings/SettingItem.qml
@@ -138,7 +138,7 @@ Item {
{
id: linkedSettingIcon;
- visible: base.settablePerExtruder != "True" && base.showLinkedSettingIcon
+ visible: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId && base.settablePerExtruder != "True" && base.showLinkedSettingIcon
height: parent.height;
width: height;
diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml
index 53a2375394..8ab776af6a 100644
--- a/resources/qml/Sidebar.qml
+++ b/resources/qml/Sidebar.qml
@@ -108,7 +108,7 @@ Rectangle
iconSource: {
if(!printerConnected)
return UM.Theme.getIcon("tab_monitor")
- else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print")
+ else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print" || Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup" )
return UM.Theme.getIcon("tab_monitor_busy")
else if(Cura.MachineManager.printerOutputDevices[0].jobState == "ready" || Cura.MachineManager.printerOutputDevices[0].jobState == "")
return UM.Theme.getIcon("tab_monitor_connected")
@@ -116,6 +116,8 @@ Rectangle
return UM.Theme.getIcon("tab_monitor_paused")
else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error")
return UM.Theme.getIcon("tab_monitor_stopped")
+ else if (Cura.MachineManager.printerOutputDevices[0].jobState == "offline")
+ return UM.Theme.getIcon("tab_monitor_offline")
else
return UM.Theme.getIcon("tab_monitor")
}
diff --git a/resources/shaders/transparent_object.shader b/resources/shaders/transparent_object.shader
index a3790901bc..cd27a40769 100644
--- a/resources/shaders/transparent_object.shader
+++ b/resources/shaders/transparent_object.shader
@@ -1,7 +1,7 @@
[shaders]
vertex =
- uniform highp mat4 u_viewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
+ uniform highp mat4 u_viewProjectionMatrix;
uniform highp mat4 u_normalMatrix;
attribute highp vec4 a_vertex;
@@ -10,7 +10,6 @@ vertex =
varying highp vec3 v_vertex;
varying highp vec3 v_normal;
- varying highp vec2 v_uvs;
void main()
{
@@ -19,56 +18,47 @@ vertex =
v_vertex = world_space_vert.xyz;
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
-
- v_uvs = a_uvs;
}
fragment =
uniform mediump vec4 u_ambientColor;
uniform mediump vec4 u_diffuseColor;
uniform highp vec3 u_lightPosition;
- uniform highp vec3 u_viewPosition;
+
uniform mediump float u_opacity;
- uniform sampler2D u_texture;
varying highp vec3 v_vertex;
varying highp vec3 v_normal;
- varying highp vec2 v_uvs;
void main()
{
- // Copied from platform.shader, removed texture
- mediump vec4 final_color = vec4(0.0);
+ mediump vec4 finalColor = vec4(0.0);
/* Ambient Component */
- final_color += u_ambientColor;
+ finalColor += u_ambientColor;
highp vec3 normal = normalize(v_normal);
- highp vec3 light_dir = normalize(u_lightPosition - v_vertex);
+ highp vec3 lightDir = normalize(u_lightPosition - v_vertex);
/* Diffuse Component */
- highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0);
- final_color += (n_dot_l * u_diffuseColor);
+ highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0);
+ finalColor += (NdotL * u_diffuseColor);
- final_color.a = u_opacity;
-
- gl_FragColor = final_color;
+ gl_FragColor = finalColor;
+ gl_FragColor.a = u_opacity;
}
[defaults]
-u_ambientColor = [0.3, 0.3, 0.3, 1.0]
-u_diffuseColor = [1.0, 1.0, 1.0, 1.0]
+u_ambientColor = [0.1, 0.1, 0.1, 1.0]
+u_diffuseColor = [0.4, 0.4, 0.4, 1.0]
u_opacity = 0.5
-u_texture = 0
[bindings]
-u_viewProjectionMatrix = view_projection_matrix
u_modelMatrix = model_matrix
+u_viewProjectionMatrix = view_projection_matrix
u_normalMatrix = normal_matrix
u_lightPosition = light_0_position
-u_viewPosition = camera_position
[attributes]
a_vertex = vertex
a_normal = normal
-a_uvs = uv0