Merge branch '4.0' into ppa_contribute_compatible_format

This commit is contained in:
Remco Burema 2019-02-20 11:26:13 +01:00
commit 7fca9dae04
26 changed files with 190 additions and 156 deletions

View File

@ -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

View File

@ -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

View File

@ -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

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.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()

View File

@ -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):

View File

@ -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()

View File

@ -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()

View File

@ -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):

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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))

View File

@ -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,

View File

@ -22,7 +22,6 @@ Item
{
id: profileImage
anchors.fill: parent
source: UM.Theme.getImage("avatar_default")
fillMode: Image.PreserveAspectCrop
visible: false
mipmap: true

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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],