Merge master into material marketplace

This commit is contained in:
Lipu Fei 2018-05-01 11:56:34 +02:00
commit 9a5fb47a6e
228 changed files with 125261 additions and 4020 deletions

View file

@ -746,12 +746,6 @@ class CuraApplication(QtApplication):
controller = self.getController()
# Initialize UI state
controller.setActiveStage("PrepareStage")
controller.setActiveView("SolidView")
controller.setCameraTool("CameraTool")
controller.setSelectionTool("SelectionTool")
t = controller.getTool("TranslateTool")
if t:
t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])
@ -788,8 +782,11 @@ class CuraApplication(QtApplication):
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
self.initializeEngine()
# Make sure the correct stage is activated after QML is loaded
# Initialize UI state
controller.setActiveStage("PrepareStage")
controller.setActiveView("SolidView")
controller.setCameraTool("CameraTool")
controller.setSelectionTool("SelectionTool")
# Hide the splash screen
self.closeSplash()
@ -1012,7 +1009,7 @@ class CuraApplication(QtApplication):
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
def updatePlatformActivityDelayed(self, node = None):
if node is not None and node.getMeshData() is not None:
if node is not None and (node.getMeshData() is not None or node.callDecoration("getLayerData")):
self._update_platform_activity_timer.start()
## Update scene bounding box for current build plate
@ -1380,7 +1377,7 @@ class CuraApplication(QtApplication):
try:
group_node = Selection.getAllSelectedObjects()[0]
except Exception as e:
Logger.log("d", "mergeSelected: Exception:", e)
Logger.log("e", "mergeSelected: Exception: %s", e)
return
meshes = [node.getMeshData() for node in group_node.getAllChildren() if node.getMeshData()]
@ -1753,3 +1750,7 @@ class CuraApplication(QtApplication):
node = node.getParent()
Selection.add(node)
@pyqtSlot()
def showMoreInformationDialogForAnonymousDataCollection(self):
self._plugin_registry.getPluginObject("SliceInfoPlugin").showMoreInfoDialog()

View file

@ -235,7 +235,7 @@ class MaterialManager(QObject):
return self._material_group_map.get(root_material_id)
def getRootMaterialIDForDiameter(self, root_material_id: str, approximate_diameter: str) -> str:
return self._material_diameter_map.get(root_material_id).get(approximate_diameter, root_material_id)
return self._material_diameter_map.get(root_material_id, {}).get(approximate_diameter, root_material_id)
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
return self._diameter_material_map.get(root_material_id)

View file

@ -69,9 +69,7 @@ class QualityManagementModel(ListModel):
# Create quality_changes group items
quality_changes_item_list = []
for quality_changes_group in quality_changes_group_dict.values():
if quality_changes_group.quality_type not in available_quality_types:
continue
quality_group = quality_group_dict[quality_changes_group.quality_type]
quality_group = quality_group_dict.get(quality_changes_group.quality_type)
item = {"name": quality_changes_group.name,
"is_read_only": False,
"quality_group": quality_group,

View file

@ -84,11 +84,14 @@ class QualitySettingsModel(ListModel):
quality_group = self._selected_quality_item["quality_group"]
quality_changes_group = self._selected_quality_item["quality_changes_group"]
if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_node = quality_group.node_for_global
else:
quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position))
settings_keys = quality_group.getAllKeys()
quality_node = None
settings_keys = set()
if quality_group:
if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_node = quality_group.node_for_global
else:
quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position))
settings_keys = quality_group.getAllKeys()
quality_containers = []
if quality_node is not None and quality_node.getContainer() is not None:
quality_containers.append(quality_node.getContainer())

View file

@ -163,7 +163,7 @@ class QualityManager(QObject):
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
used_extruders = set()
for i in range(machine.getProperty("machine_extruder_count", "value")):
if machine.extruders[str(i)].isEnabled:
if str(i) in machine.extruders and machine.extruders[str(i)].isEnabled:
used_extruders.add(str(i))
# Update the "is_available" flag for each quality group.

View file

@ -74,7 +74,11 @@ class VariantManager:
for variant_type in ALL_VARIANT_TYPES:
self._machine_to_variant_dict_map[variant_definition][variant_type] = dict()
variant_type = variant_metadata["hardware_type"]
try:
variant_type = variant_metadata["hardware_type"]
except KeyError:
Logger.log("w", "Variant %s does not specify a hardware_type; assuming 'nozzle'", variant_metadata["id"])
variant_type = VariantType.NOZZLE
variant_type = VariantType(variant_type)
variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type]
if variant_name in variant_dict:

View file

@ -32,14 +32,19 @@ class MultiplyObjectsJob(Job):
root = scene.getRoot()
arranger = Arrange.create(scene_root=root)
processed_nodes = []
nodes = []
for node in self._objects:
# If object is part of a group, multiply group
current_node = node
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
while current_node.getParent() and (current_node.getParent().callDecoration("isGroup") or current_node.getParent().callDecoration("isSliceable")):
current_node = current_node.getParent()
if current_node in processed_nodes:
continue
processed_nodes.append(current_node)
node_too_big = False
if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)

View file

@ -138,6 +138,7 @@ class PrintInformation(QObject):
def setPreSliced(self, pre_sliced):
self._pre_sliced = pre_sliced
self._updateJobName()
self.preSlicedChanged.emit()
@pyqtProperty(Duration, notify = currentPrintTimeChanged)
@ -322,15 +323,20 @@ class PrintInformation(QObject):
# when a file is opened using the terminal; the filename comes from _onFileLoaded and still contains its
# extension. This cuts the extension off if necessary.
name = os.path.splitext(name)[0]
filename_parts = os.path.basename(base_name).split(".")
# If it's a gcode, also always update the job name
is_gcode = False
if len(filename_parts) > 1:
# Only check the extension(s)
is_gcode = "gcode" in filename_parts[1:]
# if this is a profile file, always update the job name
# name is "" when I first had some meshes and afterwards I deleted them so the naming should start again
is_empty = name == ""
if is_project_file or (is_empty or (self._base_name == "" and self._base_name != name)):
# remove ".curaproject" suffix from (imported) the file name
if name.endswith(".curaproject"):
name = name[:name.rfind(".curaproject")]
self._base_name = name
if is_gcode or is_project_file or (is_empty or (self._base_name == "" and self._base_name != name)):
# Only take the file name part
self._base_name = filename_parts[0]
self._updateJobName()
@pyqtProperty(str, fset = setBaseName, notify = baseNameChanged)

View file

@ -58,8 +58,7 @@ class GenericOutputController(PrinterOutputController):
self._output_device.sendCommand("G90")
def homeHead(self, printer):
self._output_device.sendCommand("G28 X")
self._output_device.sendCommand("G28 Y")
self._output_device.sendCommand("G28 X Y")
def homeBed(self, printer):
self._output_device.sendCommand("G28 Z")
@ -152,3 +151,17 @@ class GenericOutputController(PrinterOutputController):
for extruder in self._preheat_hotends:
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
self._preheat_hotends = set()
# Cancel any ongoing preheating timers, without setting back the temperature to 0
# This can be used eg at the start of a print
def stopPreheatTimers(self):
if self._preheat_hotends_timer.isActive():
for extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
self._preheat_hotends = set()
self._preheat_hotends_timer.stop()
if self._preheat_bed_timer.isActive():
self._preheat_printer.updateIsPreheating(False)
self._preheat_bed_timer.stop()

View file

@ -59,7 +59,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
printer_type_identifiers = {
"9066": "ultimaker3",
"9511": "ultimaker3_extended"
"9511": "ultimaker3_extended",
"9051": "ultimaker_s5"
}
self._printer_type = "Unknown"
for key, value in printer_type_identifiers.items():

View file

@ -1,6 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Decorators import deprecated
from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal, QVariant
@ -175,6 +175,10 @@ class PrinterOutputDevice(QObject, OutputDevice):
def acceptsCommands(self):
return self._accepts_commands
@deprecated("Please use the protected function instead", "3.2")
def setAcceptsCommands(self, accepts_commands):
self._setAcceptsCommands(accepts_commands)
## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
def _setAcceptsCommands(self, accepts_commands):
if self._accepts_commands != accepts_commands:

View file

@ -103,6 +103,25 @@ class CuraSceneNode(SceneNode):
return True
return False
## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box
def _calculateAABB(self):
aabb = None
if self._mesh_data:
aabb = self._mesh_data.getExtents(self.getWorldTransformation())
else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
position = self.getWorldPosition()
aabb = AxisAlignedBox(minimum = position, maximum = position)
for child in self._children:
if child.callDecoration("isNonPrintingMesh"):
# Non-printing-meshes inside a group should not affect push apart or drop to build plate
continue
if aabb is None:
aabb = child.getBoundingBox()
else:
aabb = aabb + child.getBoundingBox()
self._aabb = aabb
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
def __deepcopy__(self, memo):
copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
@ -22,12 +22,10 @@ from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
from UM.MimeTypeDatabase import MimeTypeNotFoundError
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -289,7 +287,9 @@ class ContainerManager(QObject):
with open(file_url, "rt", encoding = "utf-8") as f:
container.deserialize(f.read())
except PermissionError:
return {"status": "error", "message": "Permission denied when trying to read the file"}
return {"status": "error", "message": "Permission denied when trying to read the file."}
except ContainerFormatError:
return {"status": "error", "Message": "The material file appears to be corrupt."}
except Exception as ex:
return {"status": "error", "message": str(ex)}

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
@ -10,6 +10,7 @@ from typing import Optional
from PyQt5.QtWidgets import QMessageBox
from UM.Decorators import override
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
@ -189,7 +190,7 @@ class CuraContainerRegistry(ContainerRegistry):
return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "No custom profile to import in file <filename>{0}</filename>", file_name)}
except Exception as e:
# Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name,profile_reader.getPluginId(), str(e))
Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e))
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "\n" + str(e))}
if profile_or_list:
@ -418,7 +419,6 @@ class CuraContainerRegistry(ContainerRegistry):
Logger.log("d", "Converting ContainerStack {stack} to {type}", stack = container.getId(), type = container_type)
new_stack = None
if container_type == "extruder_train":
new_stack = ExtruderStack.ExtruderStack(container.getId())
else:
@ -704,7 +704,11 @@ class CuraContainerRegistry(ContainerRegistry):
instance_container = InstanceContainer(container_id)
with open(file_path, "r", encoding = "utf-8") as f:
serialized = f.read()
instance_container.deserialize(serialized, file_path)
try:
instance_container.deserialize(serialized, file_path)
except ContainerFormatError:
Logger.logException("e", "Unable to deserialize InstanceContainer %s", file_path)
continue
self.addContainer(instance_container)
break

View file

@ -283,6 +283,13 @@ class CuraContainerStack(ContainerStack):
self._containers = new_containers
# CURA-5281
# Some stacks can have empty definition_changes containers which will cause problems.
# Make sure that all stacks here have non-empty definition_changes containers.
if isinstance(new_containers[_ContainerIndexes.DefinitionChanges], type(self._empty_instance_container)):
from cura.Settings.CuraStackBuilder import CuraStackBuilder
CuraStackBuilder.createDefinitionChangesContainer(self, self.getId() + "_settings")
## protected:
# Helper to make sure we emit a PyQt signal on container changes.

View file

@ -74,6 +74,7 @@ class MachineManager(QObject):
self._stacks_have_errors = None # type:Optional[bool]
self._empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_definition_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0]
self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0]
self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0]
@ -310,19 +311,40 @@ class MachineManager(QObject):
global_quality_changes = global_stack.qualityChanges
global_quality_changes_name = global_quality_changes.getName()
# Try to set the same quality/quality_changes as the machine specified.
# If the quality/quality_changes is not available, switch to the default or the first quality that's available.
same_quality_found = False
quality_groups = self._application.getQualityManager().getQualityGroups(global_stack)
if global_quality_changes.getId() != "empty_quality_changes":
quality_changes_groups = self._application._quality_manager.getQualityChangesGroups(global_stack)
if global_quality_changes_name in quality_changes_groups:
new_quality_changes_group = quality_changes_groups[global_quality_changes_name]
quality_changes_groups = self._application.getQualityManager().getQualityChangesGroups(global_stack)
new_quality_changes_group = quality_changes_groups.get(global_quality_changes_name)
if new_quality_changes_group is not None:
self._setQualityChangesGroup(new_quality_changes_group)
same_quality_found = True
Logger.log("i", "Machine '%s' quality changes set to '%s'",
global_stack.getName(), new_quality_changes_group.name)
else:
quality_groups = self._application._quality_manager.getQualityGroups(global_stack)
if quality_type not in quality_groups:
Logger.log("w", "Quality type [%s] not found in available qualities [%s]", quality_type, ", ".join(quality_groups.keys()))
self._setEmptyQuality()
return
new_quality_group = quality_groups[quality_type]
self._setQualityGroup(new_quality_group, empty_quality_changes = True)
new_quality_group = quality_groups.get(quality_type)
if new_quality_group is not None:
self._setQualityGroup(new_quality_group, empty_quality_changes = True)
same_quality_found = True
Logger.log("i", "Machine '%s' quality set to '%s'",
global_stack.getName(), new_quality_group.quality_type)
# Could not find the specified quality/quality_changes, switch to the preferred quality if available,
# otherwise the first quality that's available, otherwise empty (not supported).
if not same_quality_found:
Logger.log("i", "Machine '%s' could not find quality_type '%s' and quality_changes '%s'. "
"Available quality types are [%s]. Switching to default quality.",
global_stack.getName(), quality_type, global_quality_changes_name,
", ".join(quality_groups.keys()))
preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type")
quality_group = quality_groups.get(preferred_quality_type)
if quality_group is None:
if quality_groups:
quality_group = list(quality_groups.values())[0]
self._setQualityGroup(quality_group, empty_quality_changes = True)
@pyqtSlot(str)
def setActiveMachine(self, stack_id: str) -> None:
@ -1012,6 +1034,10 @@ class MachineManager(QObject):
if empty_quality_changes:
self._current_quality_changes_group = None
if quality_group is None:
self._setEmptyQuality()
return
# Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes:
@ -1123,13 +1149,15 @@ class MachineManager(QObject):
Logger.log("d", "Current quality type = [%s]", current_quality_type)
if not self.activeMaterialsCompatible():
Logger.log("i", "Active materials are not compatible, setting all qualities to empty (Not Supported).")
self._setEmptyQuality()
if current_quality_type is not None:
Logger.log("i", "Active materials are not compatible, setting all qualities to empty (Not Supported).")
self._setEmptyQuality()
return
if not available_quality_types:
Logger.log("i", "No available quality types found, setting all qualities to empty (Not Supported).")
self._setEmptyQuality()
if self._current_quality_changes_group is None:
Logger.log("i", "No available quality types found, setting all qualities to empty (Not Supported).")
self._setEmptyQuality()
return
if current_quality_type in available_quality_types:

View file

@ -82,8 +82,9 @@ class SettingInheritanceManager(QObject):
def _onActiveExtruderChanged(self):
new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack()
# if not new_active_stack:
# new_active_stack = self._global_container_stack
if not new_active_stack:
self._active_container_stack = None
return
if new_active_stack != self._active_container_stack: # Check if changed
if self._active_container_stack: # Disconnect signal from old container (if any)
@ -154,6 +155,8 @@ class SettingInheritanceManager(QObject):
has_setting_function = False
if not stack:
stack = self._active_container_stack
if not stack: #No active container stack yet!
return False
containers = []
## Check if the setting has a user state. If not, it is never overwritten.