Merge remote-tracking branch 'origin/master'

This commit is contained in:
Nino van Hooff 2020-06-23 14:47:01 +02:00
commit 2e0489c509
18 changed files with 138 additions and 36 deletions

13
.github/no-response.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 14
# Label requiring a response
responseRequiredLabel: 'Status: Needs Info'
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

View File

@ -80,8 +80,11 @@ class Arrange:
# After scaling (like up to 0.1 mm) the node might not have points
if not points.size:
continue
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
try:
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
except ValueError:
Logger.logException("w", "Unable to create polygon")
continue
arranger.place(0, 0, shape_arr)
# If a build volume was set, add the disallowed areas

View File

@ -31,7 +31,6 @@ class AutoSave:
self._change_timer.timeout.connect(self._onTimeout)
self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
self._triggerTimer()
def _triggerTimer(self, *args: Any) -> None:
if not self._saving:

View File

@ -215,6 +215,16 @@ class CrashHandler:
locale.getdefaultlocale()[0]
self.data["locale_cura"] = self.cura_locale
try:
from cura.CuraApplication import CuraApplication
plugins = CuraApplication.getInstance().getPluginRegistry()
self.data["plugins"] = {
plugin_id: plugins.getMetaData(plugin_id)["plugin"]["version"]
for plugin_id in plugins.getInstalledPlugins() if not plugins.isBundledPlugin(plugin_id)
}
except:
self.data["plugins"] = {"[FAILED]": "0.0.0"}
crash_info = "<b>" + catalog.i18nc("@label Cura version number", "Cura version") + ":</b> " + str(self.cura_version) + "<br/>"
crash_info += "<b>" + catalog.i18nc("@label", "Cura language") + ":</b> " + str(self.cura_locale) + "<br/>"
crash_info += "<b>" + catalog.i18nc("@label", "OS language") + ":</b> " + str(self.data["locale_os"]) + "<br/>"
@ -238,6 +248,8 @@ class CrashHandler:
scope.set_tag("locale_cura", self.cura_locale)
scope.set_tag("is_enterprise", ApplicationMetadata.IsEnterpriseVersion)
scope.set_context("plugins", self.data["plugins"])
scope.set_user({"id": str(uuid.getnode())})
return group

View File

@ -756,7 +756,6 @@ class CuraApplication(QtApplication):
if not hasattr(sys, "frozen"):
self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))
self._plugin_registry.loadPlugin("ConsoleLogger")
self._plugin_registry.loadPlugin("CuraEngineBackend")
self._plugin_registry.loadPlugins()

View File

@ -1,4 +1,4 @@
# Copyright (c) 2019 Ultimaker B.V.
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from copy import deepcopy
@ -119,9 +119,9 @@ class CuraSceneNode(SceneNode):
self._aabb = None
if self._mesh_data:
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation())
else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
else: # If there is no mesh_data, use a bounding box that encompasses the local (0,0,0)
position = self.getWorldPosition()
self._aabb = AxisAlignedBox(minimum=position, maximum=position)
self._aabb = AxisAlignedBox(minimum = position, maximum = position)
for child in self.getAllChildren():
if child.callDecoration("isNonPrintingMesh"):

View File

@ -60,6 +60,8 @@ class CuraContainerStack(ContainerStack):
import cura.CuraApplication #Here to prevent circular imports.
self.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion)
self.setDirty(False)
# This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted.
pyqtContainersChanged = pyqtSignal()

View File

@ -32,6 +32,8 @@ class ExtruderStack(CuraContainerStack):
self.propertiesChanged.connect(self._onPropertiesChanged)
self.setDirty(False)
enabledChanged = pyqtSignal()
@override(ContainerStack)

View File

@ -55,6 +55,8 @@ class GlobalStack(CuraContainerStack):
# properties. So we need to tie them together like this.
self.metaDataChanged.connect(self.configuredConnectionTypesChanged)
self.setDirty(False)
extrudersChanged = pyqtSignal()
configuredConnectionTypesChanged = pyqtSignal()

View File

@ -738,14 +738,15 @@ class MachineManager(QObject):
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers:
CuraContainerRegistry.getInstance().removeContainer(container["id"])
machine_stack = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", name = machine_id)[0]
CuraContainerRegistry.getInstance().removeContainer(machine_stack.definitionChanges.getId())
machine_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", name = machine_id)
if machine_stacks:
CuraContainerRegistry.getInstance().removeContainer(machine_stacks[0].definitionChanges.getId())
CuraContainerRegistry.getInstance().removeContainer(machine_id)
# If the printer that is being removed is a network printer, the hidden printers have to be also removed
group_id = metadata.get("group_id", None)
if group_id:
metadata_filter = {"group_id": group_id}
metadata_filter = {"group_id": group_id, "hidden": True}
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
if hidden_containers:
# This reuses the method and remove all printers recursively
@ -1368,7 +1369,6 @@ class MachineManager(QObject):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self.switchPrinterType(configuration.printerType)
disabled_used_extruder_position_set = set()
extruders_to_disable = set()
# If an extruder that's currently used to print a model gets disabled due to the syncing, we need to show
@ -1377,8 +1377,8 @@ class MachineManager(QObject):
for extruder_configuration in configuration.extruderConfigurations:
# We support "" or None, since the cloud uses None instead of empty strings
extruder_has_hotend = extruder_configuration.hotendID and extruder_configuration.hotendID != ""
extruder_has_material = extruder_configuration.material.guid and extruder_configuration.material.guid != ""
extruder_has_hotend = extruder_configuration.hotendID not in ["", None]
extruder_has_material = extruder_configuration.material.guid not in [None, "", "00000000-0000-0000-0000-000000000000"]
# If the machine doesn't have a hotend or material, disable this extruder
if not extruder_has_hotend or not extruder_has_material:
@ -1396,7 +1396,6 @@ class MachineManager(QObject):
self._global_container_stack.extruderList[int(position)].setEnabled(False)
need_to_show_message = True
disabled_used_extruder_position_set.add(int(position))
else:
machine_node = ContainerTree.getInstance().machines.get(self._global_container_stack.definition.getId())
@ -1427,7 +1426,7 @@ class MachineManager(QObject):
# Show human-readable extruder names such as "Extruder Left", "Extruder Front" instead of "Extruder 1, 2, 3".
extruder_names = []
for extruder_position in sorted(disabled_used_extruder_position_set):
for extruder_position in sorted(extruders_to_disable):
extruder_stack = self._global_container_stack.extruderList[int(extruder_position)]
extruder_name = extruder_stack.definition.getName()
extruder_names.append(extruder_name)

View File

@ -67,4 +67,4 @@ cmake3 \
-DBUILD_TESTS=ON \
..
make
ctest3 --output-on-failure -T Test
ctest3 -j4 --output-on-failure -T Test

View File

@ -651,7 +651,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._container_registry.addContainer(global_stack)
else:
# Find the machine
global_stack = self._container_registry.findContainerStacks(name = self._machine_info.name, type = "machine")[0]
global_stacks = self._container_registry.findContainerStacks(name = self._machine_info.name, type = "machine")
if not global_stacks:
message = Message(i18n_catalog.i18nc("@info:error Don't translate the XML tag <filename>!", "Project file <filename>{0}</filename> is made using profiles that are unknown to this version of Ultimaker Cura.", file_name))
message.show()
self.setWorkspaceName("")
return [], {}
global_stack = global_stacks[0]
extruder_stacks = self._container_registry.findContainerStacks(machine = global_stack.getId(),
type = "extruder_train")
extruder_stack_dict = {stack.getMetaDataEntry("position"): stack for stack in extruder_stacks}

View File

@ -204,10 +204,11 @@ class PauseAtHeight(Script):
"""Get the X and Y values for a layer (will be used to get X and Y of the layer after the pause)."""
lines = layer.split("\n")
for line in lines:
if self.getValue(line, "X") is not None and self.getValue(line, "Y") is not None:
x = self.getValue(line, "X")
y = self.getValue(line, "Y")
return x, y
if line.startswith(("G0", "G1", "G2", "G3")):
if self.getValue(line, "X") is not None and self.getValue(line, "Y") is not None:
x = self.getValue(line, "X")
y = self.getValue(line, "Y")
return x, y
return 0, 0
def execute(self, data: List[str]) -> List[str]:

View File

@ -1,4 +1,4 @@
# Copyright (c) 2019 Ultimaker B.V.
# Copyright (c) 2020 Ultimaker B.V.
# Toolbox is released under the terms of the LGPLv3 or higher.
import json
@ -232,7 +232,7 @@ class Toolbox(QObject, Extension):
"licenseModel": self._license_model
})
if not dialog:
raise Exception("Failed to create Marketplace dialog")
return None
return dialog
def _convertPluginMetadata(self, plugin_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:

View File

@ -31,6 +31,7 @@ class CloudOutputDeviceManager:
"""
META_CLUSTER_ID = "um_cloud_cluster_id"
META_HOST_GUID = "host_guid"
META_NETWORK_KEY = "um_network_key"
SYNC_SERVICE_NAME = "CloudOutputDeviceManager"
@ -113,13 +114,18 @@ class CloudOutputDeviceManager:
all_clusters = {c.cluster_id: c for c in clusters} # type: Dict[str, CloudClusterResponse]
online_clusters = {c.cluster_id: c for c in clusters if c.is_online} # type: Dict[str, CloudClusterResponse]
# Add the new printers in Cura. If a printer was previously added and is rediscovered, set its metadata to
# reflect that and mark the printer not removed from the account
# Add the new printers in Cura.
for device_id, cluster_data in all_clusters.items():
if device_id not in self._remote_clusters:
new_clusters.append(cluster_data)
if device_id in self._um_cloud_printers and not parseBool(self._um_cloud_printers[device_id].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")):
self._um_cloud_printers[device_id].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True)
if device_id in self._um_cloud_printers:
# Existing cloud printers may not have the host_guid meta-data entry. If that's the case, add it.
if not self._um_cloud_printers[device_id].getMetaDataEntry(self.META_HOST_GUID, None):
self._um_cloud_printers[device_id].setMetaDataEntry(self.META_HOST_GUID, cluster_data.host_guid)
# If a printer was previously not linked to the account and is rediscovered, mark the printer as linked
# to the current account
if not parseBool(self._um_cloud_printers[device_id].getMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, "true")):
self._um_cloud_printers[device_id].setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True)
self._onDevicesDiscovered(new_clusters)
# Hide the current removed_printers_message, if there is any
@ -161,11 +167,22 @@ class CloudOutputDeviceManager:
"""
new_devices = []
remote_clusters_added = False
host_guid_map = {machine.getMetaDataEntry(self.META_HOST_GUID): device_cluster_id
for device_cluster_id, machine in self._um_cloud_printers.items()
if machine.getMetaDataEntry(self.META_HOST_GUID)}
machine_manager = CuraApplication.getInstance().getMachineManager()
for cluster_data in clusters:
device = CloudOutputDevice(self._api, cluster_data)
# Create a machine if we don't already have it. Do not make it the active machine.
machine_manager = CuraApplication.getInstance().getMachineManager()
# If the machine already existed before, it will be present in the host_guid_map
if cluster_data.host_guid in host_guid_map:
machine = machine_manager.getMachine(device.printerType, {self.META_HOST_GUID: cluster_data.host_guid})
if machine and machine.getMetaDataEntry(self.META_CLUSTER_ID) != device.key:
# If the retrieved device has a different cluster_id than the existing machine, bring the existing
# machine up-to-date.
self._updateOutdatedMachine(outdated_machine = machine, new_cloud_output_device = device)
# Create a machine if we don't already have it. Do not make it the active machine.
# We only need to add it if it wasn't already added by "local" network or by cloud.
if machine_manager.getMachine(device.printerType, {self.META_CLUSTER_ID: device.key}) is None \
and machine_manager.getMachine(device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key.
@ -239,17 +256,42 @@ class CloudOutputDeviceManager:
num_hidden = len(new_devices) - max_disp_devices + 1
device_name_list = ["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices[0:num_hidden]]
device_name_list.append(self.I18N_CATALOG.i18nc("info:hidden list items", "<li>... and {} others</li>", num_hidden))
device_names = "\n".join(device_name_list)
device_names = "".join(device_name_list)
else:
device_names = "\n".join(["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices])
device_names = "".join(["<li>{} ({})</li>".format(device.name, device.printerTypeName) for device in new_devices])
message_text = self.I18N_CATALOG.i18nc(
"info:status",
"Cloud printers added from your account:\n<ul>{}</ul>",
"Cloud printers added from your account:<ul>{}</ul>",
device_names
)
message.setText(message_text)
def _updateOutdatedMachine(self, outdated_machine: GlobalStack, new_cloud_output_device: CloudOutputDevice) -> None:
"""
Update the cloud metadata of a pre-existing machine that is rediscovered (e.g. if the printer was removed and
re-added to the account) and delete the old CloudOutputDevice related to this machine.
:param outdated_machine: The cloud machine that needs to be brought up-to-date with the new data received from
the account
:param new_cloud_output_device: The new CloudOutputDevice that should be linked to the pre-existing machine
:return: None
"""
old_cluster_id = outdated_machine.getMetaDataEntry(self.META_CLUSTER_ID)
outdated_machine.setMetaDataEntry(self.META_CLUSTER_ID, new_cloud_output_device.key)
outdated_machine.setMetaDataEntry(META_UM_LINKED_TO_ACCOUNT, True)
# Cleanup the remainings of the old CloudOutputDevice(old_cluster_id)
self._um_cloud_printers[new_cloud_output_device.key] = self._um_cloud_printers.pop(old_cluster_id)
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
if old_cluster_id in output_device_manager.getOutputDeviceIds():
output_device_manager.removeOutputDevice(old_cluster_id)
if old_cluster_id in self._remote_clusters:
# We need to close the device so that it stops checking for its status
self._remote_clusters[old_cluster_id].close()
del self._remote_clusters[old_cluster_id]
self._remote_clusters[new_cloud_output_device.key] = new_cloud_output_device
def _devicesRemovedFromAccount(self, removed_device_ids: Set[str]) -> None:
"""
Removes the CloudOutputDevice from the received device ids and marks the specific printers as "removed from
@ -378,6 +420,7 @@ class CloudOutputDeviceManager:
def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack):
machine.setName(device.name)
machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
machine.setMetaDataEntry(self.META_HOST_GUID, device.clusterData.host_guid)
machine.setMetaDataEntry("group_name", device.name)
machine.setMetaDataEntry("group_size", device.clusterSize)
machine.setMetaDataEntry("removal_warning", self.I18N_CATALOG.i18nc(

View File

@ -4474,6 +4474,19 @@
"enabled": "support_enable or support_meshes_present",
"settable_per_mesh": true
},
"support_bottom_stair_step_min_slope":
{
"label": "Support Stair Step Minimum Slope Angle",
"description": "The minimum slope of the area for stair-stepping to take effect. Low values should make support easier to remove on shallower slopes, but really low values may result in some very counter-intuitive results on other parts of the model.",
"unit": "°",
"type": "float",
"default_value": 10.0,
"limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr",
"minimum_value": "0.01",
"maximum_value": "89.99",
"enabled": "support_enable or support_meshes_present",
"settable_per_mesh": true
},
"support_join_distance":
{
"label": "Support Join Distance",

View File

@ -153,6 +153,9 @@ class MockContainer(ContainerInterface, UM.PluginObject.PluginObject):
def isDirty(self):
return True
def setDirty(self, dirty):
pass
metaDataChanged = Signal()
propertyChanged = Signal()
containersChanged = Signal()

View File

@ -12,7 +12,7 @@ from unittest.mock import patch, MagicMock
import UM.Settings.ContainerRegistry #To create empty instance containers.
import UM.Settings.ContainerStack #To set the container registry the container stacks use.
from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer.
from UM.VersionUpgradeManager import FilesDataUpdateResult
from UM.Resources import Resources
Resources.addSearchPath(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "resources")))
@ -36,6 +36,7 @@ def definition_container():
assert result.getId() == uid
return result
@pytest.mark.parametrize("file_path", definition_filepaths)
def test_definitionIds(file_path):
"""
@ -45,6 +46,7 @@ def test_definitionIds(file_path):
definition_id = os.path.basename(file_path).split(".")[0]
assert " " not in definition_id # Definition IDs are not allowed to have spaces.
@pytest.mark.parametrize("file_path", definition_filepaths)
def test_noCategory(file_path):
"""
@ -57,6 +59,7 @@ def test_noCategory(file_path):
metadata = DefinitionContainer.deserializeMetadata(json, "test_container_id")
assert "category" not in metadata[0]
@pytest.mark.parametrize("file_path", machine_filepaths)
def test_validateMachineDefinitionContainer(file_path, definition_container):
"""Tests all definition containers"""
@ -65,13 +68,12 @@ def test_validateMachineDefinitionContainer(file_path, definition_container):
if file_name == "fdmprinter.def.json" or file_name == "fdmextruder.def.json":
return # Stop checking, these are root files.
from UM.VersionUpgradeManager import FilesDataUpdateResult
mocked_vum = MagicMock()
mocked_vum.updateFilesData = lambda ct, v, fdl, fnl: FilesDataUpdateResult(ct, v, fdl, fnl)
with patch("UM.VersionUpgradeManager.VersionUpgradeManager.getInstance", MagicMock(return_value = mocked_vum)):
assertIsDefinitionValid(definition_container, file_path)
def assertIsDefinitionValid(definition_container, file_path):
with open(file_path, encoding = "utf-8") as data:
json = data.read()
@ -86,6 +88,7 @@ def assertIsDefinitionValid(definition_container, file_path):
if "platform_texture" in metadata[0]:
assert metadata[0]["platform_texture"] in all_images
@pytest.mark.parametrize("file_path", definition_filepaths)
def test_validateOverridingDefaultValue(file_path: str):
"""Tests whether setting values are not being hidden by parent containers.
@ -189,7 +192,9 @@ def test_noId(file_path: str):
@pytest.mark.parametrize("file_path", extruder_filepaths)
def test_extruderMatch(file_path: str):
"""Verifies that extruders say that they work on the same extruder_nr as what is listed in their machine definition."""
"""
Verifies that extruders say that they work on the same extruder_nr as what is listed in their machine definition.
"""
extruder_id = os.path.basename(file_path).split(".")[0]
with open(file_path, encoding = "utf-8") as f: