mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-14 06:05:52 +08:00
Merge branch '4.0' into ppa_contribute_compatible_format
This commit is contained in:
commit
7fca9dae04
@ -76,6 +76,9 @@ class Account(QObject):
|
||||
self._error_message.hide()
|
||||
self._error_message = Message(error_message, title = i18n_catalog.i18nc("@info:title", "Login failed"))
|
||||
self._error_message.show()
|
||||
self._logged_in = False
|
||||
self.loginStateChanged.emit(False)
|
||||
return
|
||||
|
||||
if self._logged_in != logged_in:
|
||||
self._logged_in = logged_in
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
@ -39,10 +39,17 @@ class ArrangeObjectsJob(Job):
|
||||
|
||||
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes, min_offset = self._min_offset)
|
||||
|
||||
# Build set to exclude children (those get arranged together with the parents).
|
||||
included_as_child = set()
|
||||
for node in self._nodes:
|
||||
included_as_child.update(node.getAllChildren())
|
||||
|
||||
# Collect nodes to be placed
|
||||
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
|
||||
for node in self._nodes:
|
||||
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
|
||||
if node in included_as_child:
|
||||
continue
|
||||
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset, include_children = True)
|
||||
if offset_shape_arr is None:
|
||||
Logger.log("w", "Node [%s] could not be converted to an array for arranging...", str(node))
|
||||
continue
|
||||
|
@ -42,7 +42,7 @@ class ShapeArray:
|
||||
# \param min_offset offset for the offset ShapeArray
|
||||
# \param scale scale the coordinates
|
||||
@classmethod
|
||||
def fromNode(cls, node, min_offset, scale = 0.5):
|
||||
def fromNode(cls, node, min_offset, scale = 0.5, include_children = False):
|
||||
transform = node._transformation
|
||||
transform_x = transform._data[0][3]
|
||||
transform_y = transform._data[2][3]
|
||||
@ -52,6 +52,21 @@ class ShapeArray:
|
||||
return None, None
|
||||
# For one_at_a_time printing you need the convex hull head.
|
||||
hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts
|
||||
if hull_head_verts is None:
|
||||
hull_head_verts = Polygon()
|
||||
|
||||
# If the child-nodes are included, adjust convex hulls as well:
|
||||
if include_children:
|
||||
children = node.getAllChildren()
|
||||
if not children is None:
|
||||
for child in children:
|
||||
# 'Inefficient' combination of convex hulls through known code rather than mess it up:
|
||||
child_hull = child.callDecoration("getConvexHull")
|
||||
if not child_hull is None:
|
||||
hull_verts = hull_verts.unionConvexHulls(child_hull)
|
||||
child_hull_head = child.callDecoration("getConvexHullHead") or child_hull
|
||||
if not child_hull_head is None:
|
||||
hull_head_verts = hull_head_verts.unionConvexHulls(child_hull_head)
|
||||
|
||||
offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
|
||||
offset_points = copy.deepcopy(offset_verts._points) # x, y
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Scene.Camera import Camera
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from UM.Application import Application #To modify the maximum zoom level.
|
||||
@ -112,8 +112,6 @@ class BuildVolume(SceneNode):
|
||||
self._setting_change_timer.setSingleShot(True)
|
||||
self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished)
|
||||
|
||||
|
||||
|
||||
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
|
||||
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
|
||||
# Therefore this works.
|
||||
@ -131,6 +129,8 @@ class BuildVolume(SceneNode):
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
if self._global_container_stack:
|
||||
# Ignore anything that is not something we can slice in the first place!
|
||||
if source.callDecoration("isSliceable"):
|
||||
self._scene_change_timer.start()
|
||||
|
||||
def _onSceneChangeTimerFinished(self):
|
||||
@ -148,7 +148,7 @@ class BuildVolume(SceneNode):
|
||||
if active_extruder_changed is not None:
|
||||
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
|
||||
node.decoratorsChanged.disconnect(self._updateNodeListeners)
|
||||
self._updateDisallowedAreasAndRebuild() # make sure we didn't miss anything before we updated the node listeners
|
||||
self.rebuild()
|
||||
|
||||
self._scene_objects = new_scene_objects
|
||||
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
|
||||
@ -667,6 +667,7 @@ class BuildVolume(SceneNode):
|
||||
# ``_updateDisallowedAreas`` method itself shouldn't call ``rebuild``,
|
||||
# since there may be other changes before it needs to be rebuilt, which
|
||||
# would hit performance.
|
||||
|
||||
def _updateDisallowedAreasAndRebuild(self):
|
||||
self._updateDisallowedAreas()
|
||||
self._updateRaftThickness()
|
||||
|
@ -4,6 +4,7 @@
|
||||
from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
@ -34,6 +35,7 @@ class MultiBuildPlateModel(ListModel):
|
||||
self._active_build_plate = -1
|
||||
|
||||
def setMaxBuildPlate(self, max_build_plate):
|
||||
if self._max_build_plate != max_build_plate:
|
||||
self._max_build_plate = max_build_plate
|
||||
self.maxBuildPlateChanged.emit()
|
||||
|
||||
@ -43,6 +45,7 @@ class MultiBuildPlateModel(ListModel):
|
||||
return self._max_build_plate
|
||||
|
||||
def setActiveBuildPlate(self, nr):
|
||||
if self._active_build_plate != nr:
|
||||
self._active_build_plate = nr
|
||||
self.activeBuildPlateChanged.emit()
|
||||
|
||||
@ -51,6 +54,7 @@ class MultiBuildPlateModel(ListModel):
|
||||
return self._active_build_plate
|
||||
|
||||
def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
|
||||
if not isinstance(args[0], Camera):
|
||||
self._update_timer.start()
|
||||
|
||||
def _updateSelectedObjectBuildPlateNumbers(self, *args):
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
import webbrowser
|
||||
from datetime import datetime, timedelta
|
||||
@ -9,12 +10,16 @@ import requests.exceptions
|
||||
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal
|
||||
|
||||
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
|
||||
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT
|
||||
from cura.OAuth2.Models import AuthenticationResponse
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.OAuth2.Models import UserProfile, OAuth2Settings
|
||||
from UM.Preferences import Preferences
|
||||
@ -41,6 +46,14 @@ class AuthorizationService:
|
||||
self._preferences = preferences
|
||||
self._server = LocalAuthorizationServer(self._auth_helpers, self._onAuthStateChanged, daemon=True)
|
||||
|
||||
self._unable_to_get_data_message = None # type: Optional[Message]
|
||||
|
||||
self.onAuthStateChanged.connect(self._authChanged)
|
||||
|
||||
def _authChanged(self, logged_in):
|
||||
if logged_in and self._unable_to_get_data_message is not None:
|
||||
self._unable_to_get_data_message.hide()
|
||||
|
||||
def initialize(self, preferences: Optional["Preferences"] = None) -> None:
|
||||
if preferences is not None:
|
||||
self._preferences = preferences
|
||||
@ -162,7 +175,18 @@ class AuthorizationService:
|
||||
preferences_data = json.loads(self._preferences.getValue(self._settings.AUTH_DATA_PREFERENCE_KEY))
|
||||
if preferences_data:
|
||||
self._auth_data = AuthenticationResponse(**preferences_data)
|
||||
# Also check if we can actually get the user profile information.
|
||||
user_profile = self.getUserProfile()
|
||||
if user_profile is not None:
|
||||
self.onAuthStateChanged.emit(logged_in=True)
|
||||
else:
|
||||
if self._unable_to_get_data_message is not None:
|
||||
self._unable_to_get_data_message.hide()
|
||||
|
||||
self._unable_to_get_data_message = Message(i18n_catalog.i18nc("@info", "Unable to reach the Ultimaker account server."), title = i18n_catalog.i18nc("@info:title", "Warning"))
|
||||
self._unable_to_get_data_message.addAction("retry", i18n_catalog.i18nc("@action:button", "Retry"), "[no_icon]", "[no_description]")
|
||||
self._unable_to_get_data_message.actionTriggered.connect(self._onMessageActionTriggered)
|
||||
self._unable_to_get_data_message.show()
|
||||
except ValueError:
|
||||
Logger.logException("w", "Could not load auth data from preferences")
|
||||
|
||||
@ -179,3 +203,7 @@ class AuthorizationService:
|
||||
else:
|
||||
self._user_profile = None
|
||||
self._preferences.resetPreference(self._settings.AUTH_DATA_PREFERENCE_KEY)
|
||||
|
||||
def _onMessageActionTriggered(self, _, action):
|
||||
if action == "retry":
|
||||
self.loadAuthDataFromPreferences()
|
||||
|
@ -5,6 +5,7 @@ from PyQt5.QtCore import QTimer
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Selection import Selection
|
||||
@ -19,20 +20,25 @@ class ObjectsModel(ListModel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed)
|
||||
Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed)
|
||||
|
||||
self._update_timer = QTimer()
|
||||
self._update_timer.setInterval(100)
|
||||
self._update_timer.setInterval(200)
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._update)
|
||||
|
||||
self._build_plate_number = -1
|
||||
|
||||
def setActiveBuildPlate(self, nr):
|
||||
if self._build_plate_number != nr:
|
||||
self._build_plate_number = nr
|
||||
self._update()
|
||||
|
||||
def _updateSceneDelayed(self, source):
|
||||
if not isinstance(source, Camera):
|
||||
self._update_timer.start()
|
||||
|
||||
def _updateDelayed(self, *args):
|
||||
self._update_timer.start()
|
||||
|
||||
|
@ -17,7 +17,6 @@ from cura.Scene import ZOffsetDecorator
|
||||
|
||||
import random # used for list shuffling
|
||||
|
||||
|
||||
class PlatformPhysics:
|
||||
def __init__(self, controller, volume):
|
||||
super().__init__()
|
||||
@ -40,8 +39,9 @@ class PlatformPhysics:
|
||||
Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True)
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
if not source.getMeshData():
|
||||
if not source.callDecoration("isSliceable"):
|
||||
return
|
||||
|
||||
self._change_timer.start()
|
||||
|
||||
def _onChangeTimerFinished(self):
|
||||
|
@ -44,7 +44,7 @@ class PrinterOutputModel(QObject):
|
||||
self._printer_state = "unknown"
|
||||
self._is_preheating = False
|
||||
self._printer_type = ""
|
||||
self._buildplate_name = ""
|
||||
self._buildplate = ""
|
||||
|
||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||
self._extruders]
|
||||
@ -86,12 +86,12 @@ class PrinterOutputModel(QObject):
|
||||
|
||||
@pyqtProperty(str, notify = buildplateChanged)
|
||||
def buildplate(self) -> str:
|
||||
return self._buildplate_name
|
||||
return self._buildplate
|
||||
|
||||
def updateBuildplateName(self, buildplate_name: str) -> None:
|
||||
if self._buildplate_name != buildplate_name:
|
||||
self._buildplate_name = buildplate_name
|
||||
self._printer_configuration.buildplateConfiguration = self._buildplate_name
|
||||
def updateBuildplate(self, buildplate: str) -> None:
|
||||
if self._buildplate != buildplate:
|
||||
self._buildplate = buildplate
|
||||
self._printer_configuration.buildplateConfiguration = self._buildplate
|
||||
self.buildplateChanged.emit()
|
||||
self.configurationChanged.emit()
|
||||
|
||||
|
@ -3,6 +3,7 @@ from UM.Logger import Logger
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, QObject
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from UM.Scene.Camera import Camera
|
||||
from cura.ObjectsModel import ObjectsModel
|
||||
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||
|
||||
@ -33,7 +34,7 @@ class CuraSceneController(QObject):
|
||||
source = args[0]
|
||||
else:
|
||||
source = None
|
||||
if not isinstance(source, SceneNode):
|
||||
if not isinstance(source, SceneNode) or isinstance(source, Camera):
|
||||
return
|
||||
max_build_plate = self._calcMaxBuildPlate()
|
||||
changed = False
|
||||
|
@ -1,3 +1,4 @@
|
||||
import functools
|
||||
import threading
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
@ -6,7 +7,7 @@ from cura.CuraApplication import CuraApplication
|
||||
#
|
||||
# HACK:
|
||||
#
|
||||
# In project loading, when override the existing machine is selected, the stacks and containers that are correctly
|
||||
# In project loading, when override the existing machine is selected, the stacks and containers that are currently
|
||||
# active in the system will be overridden at runtime. Because the project loading is done in a different thread than
|
||||
# the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access
|
||||
# the current stacks and container, which have not completely been updated yet, so Cura will crash in this case.
|
||||
@ -22,7 +23,13 @@ class InterCallObject:
|
||||
|
||||
|
||||
def call_on_qt_thread(func):
|
||||
@functools.wraps(func)
|
||||
def _call_on_qt_thread_wrapper(*args, **kwargs):
|
||||
# If the current thread is the main thread, which is the Qt thread, directly call the function.
|
||||
current_thread = threading.current_thread()
|
||||
if isinstance(current_thread, threading._MainThread):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
def _handle_call(ico, *args, **kwargs):
|
||||
ico.result = func(*args, **kwargs)
|
||||
ico.finish_event.set()
|
||||
|
@ -40,10 +40,13 @@ class DriveApiService:
|
||||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return []
|
||||
|
||||
try:
|
||||
backup_list_request = requests.get(self.BACKUP_URL, headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
except requests.exceptions.ConnectionError:
|
||||
Logger.log("w", "Unable to connect with the server.")
|
||||
return []
|
||||
|
||||
# HTTP status 300s mean redirection. 400s and 500s are errors.
|
||||
# Technically 300s are not errors, but the use case here relies on "requests" to handle redirects automatically.
|
||||
|
@ -10,6 +10,7 @@ from time import time
|
||||
from typing import Any, cast, Dict, List, Optional, Set, TYPE_CHECKING
|
||||
|
||||
from UM.Backend.Backend import Backend, BackendState
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Signal import Signal
|
||||
from UM.Logger import Logger
|
||||
@ -476,7 +477,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||
#
|
||||
# \param source The scene node that was changed.
|
||||
def _onSceneChanged(self, source: SceneNode) -> None:
|
||||
if not isinstance(source, SceneNode):
|
||||
if not source.callDecoration("isSliceable"):
|
||||
return
|
||||
|
||||
# This case checks if the source node is a node that contains GCode. In this case the
|
||||
|
@ -9,6 +9,7 @@ from UM.Application import Application
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
@ -35,6 +36,11 @@ class ModelChecker(QObject, Extension):
|
||||
|
||||
## Pass-through to allow UM.Signal to connect with a pyqtSignal.
|
||||
def _onChanged(self, *args, **kwargs):
|
||||
# Ignore camera updates.
|
||||
if len(args) == 0:
|
||||
self.onChanged.emit()
|
||||
return
|
||||
if not isinstance(args[0], Camera):
|
||||
self.onChanged.emit()
|
||||
|
||||
## Called when plug-ins are initialized.
|
||||
|
@ -69,8 +69,7 @@ class SolidView(View):
|
||||
if support_angle_stack is not None and Application.getInstance().getPreferences().getValue("view/show_overhang"):
|
||||
angle = support_angle_stack.getProperty("support_angle", "value")
|
||||
# Make sure the overhang angle is valid before passing it to the shader
|
||||
# Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)
|
||||
if angle is not None and global_container_stack.getProperty("support_angle", "validationState") in [None, ValidatorState.Valid]:
|
||||
if angle is not None and angle >= 0 and angle <= 90:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle)))
|
||||
else:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.
|
||||
|
@ -61,8 +61,19 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
||||
# \param cluster: The device response received from the cloud API.
|
||||
# \param parent: The optional parent of this output device.
|
||||
def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None:
|
||||
|
||||
# The following properties are expected on each networked output device.
|
||||
# Because the cloud connection does not off all of these, we manually construct this version here.
|
||||
# An example of why this is needed is the selection of the compatible file type when exporting the tool path.
|
||||
properties = {
|
||||
b"address": b"",
|
||||
b"name": cluster.host_name.encode() if cluster.host_name else b"",
|
||||
b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
|
||||
b"printer_type": b""
|
||||
}
|
||||
|
||||
super().__init__(device_id = cluster.cluster_id, address = "",
|
||||
connection_type = ConnectionType.CloudConnection, properties = {}, parent = parent)
|
||||
connection_type = ConnectionType.CloudConnection, properties = properties, parent = parent)
|
||||
self._api = api_client
|
||||
self._cluster = cluster
|
||||
|
||||
|
@ -65,6 +65,7 @@ class CloudClusterPrinterStatus(BaseCloudModel):
|
||||
model.updateName(self.friendly_name)
|
||||
model.updateType(self.machine_variant)
|
||||
model.updateState(self.status if self.enabled else "disabled")
|
||||
model.updateBuildplate(self.build_plate.type if self.build_plate else "glass")
|
||||
|
||||
for configuration, extruder_output, extruder_config in \
|
||||
zip(self.configuration, model.extruders, model.printerConfiguration.extruderConfigurations):
|
||||
|
@ -627,7 +627,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||
|
||||
# Do not store the build plate information that comes from connect if the current printer has not build plate information
|
||||
if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
|
||||
printer.updateBuildplateName(data["build_plate"]["type"])
|
||||
printer.updateBuildplate(data["build_plate"]["type"])
|
||||
if not data["enabled"]:
|
||||
printer.updateState("disabled")
|
||||
else:
|
||||
|
@ -455,8 +455,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||
self._start_cloud_flow_message = Message(
|
||||
text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||
lifetime = 0,
|
||||
image_source = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg",
|
||||
"cloud-flow-start.svg"),
|
||||
image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
|
||||
"resources", "svg", "cloud-flow-start.svg")),
|
||||
image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"),
|
||||
option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."),
|
||||
option_state = False
|
||||
@ -477,8 +477,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||
self._cloud_flow_complete_message = Message(
|
||||
text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||
lifetime = 30,
|
||||
image_source = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg",
|
||||
"cloud-flow-completed.svg"),
|
||||
image_source = QUrl.fromLocalFile(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
|
||||
"resources", "svg", "cloud-flow-completed.svg")),
|
||||
image_caption = i18n_catalog.i18nc("@info:status", "Connected!")
|
||||
)
|
||||
self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon
|
||||
@ -491,10 +491,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||
active_machine.setMetaDataEntry("do_not_show_cloud_message", True)
|
||||
return
|
||||
|
||||
def _onDontAskMeAgain(self, messageId: str) -> None:
|
||||
def _onDontAskMeAgain(self, checked: bool) -> None:
|
||||
active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
|
||||
if active_machine:
|
||||
active_machine.setMetaDataEntry("do_not_show_cloud_message", True)
|
||||
active_machine.setMetaDataEntry("do_not_show_cloud_message", checked)
|
||||
if checked:
|
||||
Logger.log("d", "Will not ask the user again to cloud connect for current printer.")
|
||||
return
|
||||
|
||||
|
@ -21,6 +21,7 @@ class TestCloudOutputDevice(TestCase):
|
||||
JOB_ID = "ABCDefGHIjKlMNOpQrSTUvYxWZ0-1234567890abcDE="
|
||||
HOST_NAME = "ultimakersystem-ccbdd30044ec"
|
||||
HOST_GUID = "e90ae0ac-1257-4403-91ee-a44c9b7e8050"
|
||||
HOST_VERSION = "5.2.0"
|
||||
|
||||
STATUS_URL = "{}/connect/v1/clusters/{}/status".format(CuraCloudAPIRoot, CLUSTER_ID)
|
||||
PRINT_URL = "{}/connect/v1/clusters/{}/print/{}".format(CuraCloudAPIRoot, CLUSTER_ID, JOB_ID)
|
||||
@ -36,7 +37,7 @@ class TestCloudOutputDevice(TestCase):
|
||||
patched_method.start()
|
||||
|
||||
self.cluster = CloudClusterResponse(self.CLUSTER_ID, self.HOST_GUID, self.HOST_NAME, is_online=True,
|
||||
status="active")
|
||||
status="active", host_version=self.HOST_VERSION)
|
||||
|
||||
self.network = NetworkManagerMock()
|
||||
self.account = MagicMock(isLoggedIn=True, accessToken="TestAccessToken")
|
||||
@ -56,6 +57,11 @@ class TestCloudOutputDevice(TestCase):
|
||||
for patched_method in self.patches:
|
||||
patched_method.stop()
|
||||
|
||||
# We test for these in order to make sure the correct file type is selected depending on the firmware version.
|
||||
def test_properties(self):
|
||||
self.assertEqual(self.device.firmwareVersion, self.HOST_VERSION)
|
||||
self.assertEqual(self.device.name, self.HOST_NAME)
|
||||
|
||||
def test_status(self):
|
||||
self.device._update()
|
||||
self.network.flushReplies()
|
||||
@ -114,7 +120,7 @@ class TestCloudOutputDevice(TestCase):
|
||||
|
||||
def test_print_to_cloud(self):
|
||||
active_machine_mock = self.app.getGlobalContainerStack.return_value
|
||||
active_machine_mock.getMetaDataEntry.side_effect = {"file_formats": "application/gzip"}.get
|
||||
active_machine_mock.getMetaDataEntry.side_effect = {"file_formats": "application/x-ufp"}.get
|
||||
|
||||
request_upload_response = parseFixture("putJobUploadResponse")
|
||||
request_print_response = parseFixture("postJobPrintResponse")
|
||||
@ -124,6 +130,10 @@ class TestCloudOutputDevice(TestCase):
|
||||
|
||||
file_handler = MagicMock()
|
||||
file_handler.getSupportedFileTypesWrite.return_value = [{
|
||||
"extension": "ufp",
|
||||
"mime_type": "application/x-ufp",
|
||||
"mode": 2
|
||||
}, {
|
||||
"extension": "gcode.gz",
|
||||
"mime_type": "application/gzip",
|
||||
"mode": 2,
|
||||
@ -137,10 +147,9 @@ class TestCloudOutputDevice(TestCase):
|
||||
|
||||
self.network.flushReplies()
|
||||
self.assertEqual(
|
||||
{"data": {"content_type": "application/gzip", "file_size": len(expected_mesh), "job_name": "FileName"}},
|
||||
{"data": {"content_type": "application/x-ufp", "file_size": len(expected_mesh), "job_name": "FileName"}},
|
||||
json.loads(self.network.getRequestBody("PUT", self.REQUEST_UPLOAD_URL).decode())
|
||||
)
|
||||
self.assertEqual(expected_mesh,
|
||||
self.network.getRequestBody("PUT", request_upload_response["data"]["upload_url"]))
|
||||
|
||||
self.assertIsNone(self.network.getRequestBody("POST", self.PRINT_URL))
|
||||
|
@ -1792,7 +1792,7 @@
|
||||
"skin_overlap":
|
||||
{
|
||||
"label": "Skin Overlap Percentage",
|
||||
"description": "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall.",
|
||||
"description": "Adjust the amount of overlap between the walls and (the endpoints of) the skin-centerlines, as a percentage of the line widths of the skin lines and the innermost wall. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any percentage over 50% may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.",
|
||||
"unit": "%",
|
||||
"type": "float",
|
||||
"default_value": 5,
|
||||
@ -1807,7 +1807,7 @@
|
||||
"skin_overlap_mm":
|
||||
{
|
||||
"label": "Skin Overlap",
|
||||
"description": "The amount of overlap between the skin and the walls. A slight overlap allows the walls to connect firmly to the skin.",
|
||||
"description": "Adjust the amount of overlap between the walls and (the endpoints of) the skin-centerlines. A slight overlap allows the walls to connect firmly to the skin. Note that, given an equal skin and wall line-width, any value over half the width of the wall may already cause any skin to go past the wall, because at that point the position of the nozzle of the skin-extruder may already reach past the middle of the wall.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.02,
|
||||
|
@ -22,7 +22,6 @@ Item
|
||||
{
|
||||
id: profileImage
|
||||
anchors.fill: parent
|
||||
source: UM.Theme.getImage("avatar_default")
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
visible: false
|
||||
mipmap: true
|
||||
|
@ -90,6 +90,10 @@ Item
|
||||
cornerRadius: 0
|
||||
hoverColor: UM.Theme.getColor("primary")
|
||||
Layout.fillWidth: true
|
||||
// The total width of the popup should be defined by the largest button. By stating that each
|
||||
// button should be minimally the size of it's content (aka; implicitWidth) we can ensure that.
|
||||
Layout.minimumWidth: implicitWidth
|
||||
Layout.preferredHeight: widget.height
|
||||
onClicked:
|
||||
{
|
||||
UM.OutputDeviceManager.setActiveDevice(model.id)
|
||||
|
@ -21,26 +21,29 @@ UM.Dialog
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: header
|
||||
width: parent.width + 2 * margin // margin from Dialog.qml
|
||||
height: version.y + version.height + margin
|
||||
height: childrenRect.height + topPadding
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: - margin
|
||||
anchors.topMargin: -margin
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
property real topPadding: UM.Theme.getSize("wide_margin").height
|
||||
|
||||
color: UM.Theme.getColor("main_window_header_background")
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
id: logo
|
||||
width: (base.minimumWidth * 0.85) | 0
|
||||
height: (width * (UM.Theme.getSize("logo").height / UM.Theme.getSize("logo").width)) | 0
|
||||
source: UM.Theme.getImage("logo")
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: ((base.minimumWidth - width) / 2) | 0
|
||||
anchors.topMargin: parent.topPadding
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
UM.I18nCatalog{id: catalog; name: "cura"}
|
||||
@ -57,6 +60,7 @@ UM.Dialog
|
||||
anchors.top: logo.bottom
|
||||
anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
@ -67,7 +71,7 @@ UM.Dialog
|
||||
text: catalog.i18nc("@label","End-to-end solution for fused filament 3D printing.")
|
||||
font: UM.Theme.getFont("system")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.top: version.bottom
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
|
||||
|
@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="500"
|
||||
height="500"
|
||||
viewBox="0 0 132.29167 132.29167"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="avatar_default.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="264.32988"
|
||||
inkscape:cy="275.53798"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:pagecheckerboard="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1137"
|
||||
inkscape:window-x="2872"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-164.70834)">
|
||||
<g
|
||||
id="g819"
|
||||
transform="matrix(2.4332992,0,0,2.4332992,27.213046,191.79972)"
|
||||
style="fill:#098cbc;fill-opacity:1">
|
||||
<path
|
||||
id="path815"
|
||||
d="m 16,15.7 c 3.6,0 6.4,-3.5 6.4,-7.8 C 22.4,3.6 21.5,0 16,0 10.5,0 9.6,3.5 9.6,7.8 c 0,4.4 2.8,7.9 6.4,7.9 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#098cbc;fill-opacity:1" />
|
||||
<path
|
||||
id="path817"
|
||||
d="m 28.2,27.3 c -0.1,-7.5 -1.1,-9.7 -8.6,-11 -0.9,0.9 -2.2,1.4 -3.5,1.3 -1.3,0.1 -2.6,-0.4 -3.5,-1.3 C 5,17.6 4,19.7 3.8,27 c 0,0.2 0,0.4 0,0.6 v 0.8 c 0,0 1.8,3.7 12.2,3.7 10.4,0 12.2,-3.6 12.2,-3.6 v -0.6 c 0,-0.3 -0.1,-0.4 0,-0.6 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#098cbc;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.5 KiB |
@ -473,7 +473,7 @@
|
||||
"default_lining": [0.08, 0.08],
|
||||
|
||||
"default_arrow": [0.8, 0.8],
|
||||
"logo": [8, 2.4],
|
||||
"logo": [8, 1.75],
|
||||
|
||||
"wide_margin": [2.0, 2.0],
|
||||
"thick_margin": [1.71, 1.43],
|
||||
|
Loading…
x
Reference in New Issue
Block a user