mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-02 20:52:20 -07:00
Merge remote-tracking branch 'origin/CURA-6522_one_at_a_time_overlapping_build_area' into CURA-6522_one_at_a_time_overlapping_build_area
This commit is contained in:
commit
b30b641034
1350 changed files with 16424 additions and 11601 deletions
|
|
@ -13,8 +13,8 @@ UM.Dialog
|
|||
id: base
|
||||
title: catalog.i18nc("@title:window", "Open Project")
|
||||
|
||||
minimumWidth: 500 * screenScaleFactor
|
||||
minimumHeight: 450 * screenScaleFactor
|
||||
minimumWidth: UM.Theme.getSize("popup_dialog").width
|
||||
minimumHeight: UM.Theme.getSize("popup_dialog").height
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ except ImportError:
|
|||
from . import ThreeMFWorkspaceReader
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Platform import Platform
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Window
|
|||
minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height)
|
||||
maximumWidth: Math.round(minimumWidth * 1.2)
|
||||
maximumHeight: Math.round(minimumHeight * 1.2)
|
||||
modality: Qt.ApplicationModal
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
color: UM.Theme.getColor("main_background")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import numpy
|
||||
|
|
@ -72,7 +72,7 @@ class GcodeStartEndFormatter(Formatter):
|
|||
value = default_value_str
|
||||
# "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value.
|
||||
if key in kwargs["-1"]:
|
||||
value = kwargs["-1"]
|
||||
value = kwargs["-1"][key]
|
||||
if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]:
|
||||
value = kwargs[str(extruder_nr)][key]
|
||||
|
||||
|
|
|
|||
|
|
@ -143,6 +143,52 @@ UM.Dialog
|
|||
}
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
Layout.fillWidth:true
|
||||
height: childrenRect.height
|
||||
text: catalog.i18nc("@info:tooltip","For lithophanes a simple logarithmic model for translucency is available. For height maps the pixel values correspond to heights linearly.")
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
Label {
|
||||
text: "Color Model"
|
||||
width: 150 * screenScaleFactor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
ComboBox {
|
||||
id: color_model
|
||||
objectName: "ColorModel"
|
||||
model: [ catalog.i18nc("@item:inlistbox","Linear"), catalog.i18nc("@item:inlistbox","Translucency") ]
|
||||
width: 180 * screenScaleFactor
|
||||
onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
Layout.fillWidth:true
|
||||
height: childrenRect.height
|
||||
text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.")
|
||||
visible: color_model.currentText == catalog.i18nc("@item:inlistbox","Translucency")
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
Label {
|
||||
text: catalog.i18nc("@action:label", "1mm Transmittance (%)")
|
||||
width: 150 * screenScaleFactor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
TextField {
|
||||
id: transmittance
|
||||
objectName: "Transmittance"
|
||||
focus: true
|
||||
validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/}
|
||||
width: 180 * screenScaleFactor
|
||||
onTextChanged: { manager.onTransmittanceChanged(text) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
Layout.fillWidth:true
|
||||
height: childrenRect.height
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import numpy
|
||||
|
||||
import math
|
||||
|
||||
from PyQt5.QtGui import QImage, qRed, qGreen, qBlue
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
|
|
@ -46,9 +48,9 @@ class ImageReader(MeshReader):
|
|||
|
||||
def _read(self, file_name):
|
||||
size = max(self._ui.getWidth(), self._ui.getDepth())
|
||||
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher)
|
||||
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_transparency_model, self._ui.transmittance_1mm)
|
||||
|
||||
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher):
|
||||
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_transparency_model, transmittance_1mm):
|
||||
scene_node = SceneNode()
|
||||
|
||||
mesh = MeshBuilder()
|
||||
|
|
@ -99,12 +101,14 @@ class ImageReader(MeshReader):
|
|||
for x in range(0, width):
|
||||
for y in range(0, height):
|
||||
qrgb = img.pixel(x, y)
|
||||
avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
|
||||
height_data[y, x] = avg
|
||||
if use_transparency_model:
|
||||
height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2))
|
||||
else:
|
||||
height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma
|
||||
|
||||
Job.yieldThread()
|
||||
|
||||
if not lighter_is_higher:
|
||||
if lighter_is_higher == use_transparency_model:
|
||||
height_data = 1 - height_data
|
||||
|
||||
for _ in range(0, blur_iterations):
|
||||
|
|
@ -124,8 +128,15 @@ class ImageReader(MeshReader):
|
|||
|
||||
Job.yieldThread()
|
||||
|
||||
height_data *= scale_vector.y
|
||||
height_data += base_height
|
||||
if use_transparency_model:
|
||||
divisor = 1.0 / math.log(transmittance_1mm / 100.0) # log-base doesn't matter here. Precompute this value for faster computation of each pixel.
|
||||
min_luminance = (transmittance_1mm / 100.0) ** (peak_height - base_height)
|
||||
for (y, x) in numpy.ndindex(height_data.shape):
|
||||
mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x]
|
||||
height_data[y, x] = base_height + divisor * math.log(mapped_luminance) # use same base as a couple lines above this
|
||||
else:
|
||||
height_data *= scale_vector.y
|
||||
height_data += base_height
|
||||
|
||||
heightmap_face_count = 2 * height_minus_one * width_minus_one
|
||||
total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ class ImageReaderUI(QObject):
|
|||
self.peak_height = 2.5
|
||||
self.smoothing = 1
|
||||
self.lighter_is_higher = False;
|
||||
self.use_transparency_model = True;
|
||||
self.transmittance_1mm = 50.0; # based on pearl PLA
|
||||
|
||||
self._ui_lock = threading.Lock()
|
||||
self._cancelled = False
|
||||
|
|
@ -75,6 +77,7 @@ class ImageReaderUI(QObject):
|
|||
|
||||
self._ui_view.findChild(QObject, "Base_Height").setProperty("text", str(self.base_height))
|
||||
self._ui_view.findChild(QObject, "Peak_Height").setProperty("text", str(self.peak_height))
|
||||
self._ui_view.findChild(QObject, "Transmittance").setProperty("text", str(self.transmittance_1mm))
|
||||
self._ui_view.findChild(QObject, "Smoothing").setProperty("value", self.smoothing)
|
||||
|
||||
def _createConfigUI(self):
|
||||
|
|
@ -144,3 +147,11 @@ class ImageReaderUI(QObject):
|
|||
@pyqtSlot(int)
|
||||
def onImageColorInvertChanged(self, value):
|
||||
self.lighter_is_higher = (value == 1)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def onColorModelChanged(self, value):
|
||||
self.use_transparency_model = (value == 0)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def onTransmittanceChanged(self, value):
|
||||
self.transmittance_1mm = value
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class LegacyProfileReader(ProfileReader):
|
|||
data = stream.getvalue()
|
||||
|
||||
profile = InstanceContainer(profile_id)
|
||||
profile.deserialize(data) # Also performs the version upgrade.
|
||||
profile.deserialize(data, file_name) # Also performs the version upgrade.
|
||||
profile.setDirty(True)
|
||||
|
||||
#We need to return one extruder stack and one global stack.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import configparser # An input for some functions we're testing.
|
|||
import os.path # To find the integration test .ini files.
|
||||
import pytest # To register tests with.
|
||||
import unittest.mock # To mock the application, plug-in and container registry out.
|
||||
import os.path
|
||||
import sys
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
|
|
@ -16,6 +15,7 @@ import UM.Settings.InstanceContainer # To intercept the serialised data from the
|
|||
|
||||
import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module.
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def legacy_profile_reader():
|
||||
try:
|
||||
|
|
@ -162,7 +162,7 @@ def test_read(legacy_profile_reader, file_name):
|
|||
plugin_registry.getPluginPath = unittest.mock.MagicMock(return_value = os.path.dirname(LegacyProfileReaderModule.__file__))
|
||||
|
||||
# Mock out the resulting InstanceContainer so that we can intercept the data before it's passed through the version upgrader.
|
||||
def deserialize(self, data): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing.
|
||||
def deserialize(self, data, filename): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing.
|
||||
global intercepted_data
|
||||
intercepted_data = data
|
||||
|
||||
|
|
@ -192,4 +192,4 @@ def test_read(legacy_profile_reader, file_name):
|
|||
assert parser["metadata"]["type"] == "quality_changes"
|
||||
assert parser["metadata"]["quality_type"] == "normal"
|
||||
assert parser["metadata"]["position"] == "0"
|
||||
assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded.
|
||||
assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded.
|
||||
|
|
|
|||
|
|
@ -87,9 +87,25 @@ Cura.MachineAction
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: machineNameLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
text: Cura.MachineManager.activeMachine.name
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font: UM.Theme.getFont("large_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
UM.TabRow
|
||||
{
|
||||
id: tabBar
|
||||
anchors.top: machineNameLabel.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
Repeater
|
||||
{
|
||||
|
|
|
|||
|
|
@ -331,6 +331,18 @@ Item
|
|||
onGlobalContainerChanged: extruderCountModel.update()
|
||||
}
|
||||
}
|
||||
|
||||
Cura.SimpleCheckBox // "Shared Heater"
|
||||
{
|
||||
id: sharedHeaterCheckBox
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_extruders_share_heater"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Shared Heater")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtProperty
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
||||
from UM.Application import Application
|
||||
|
|
@ -13,6 +13,7 @@ import UM.Settings.Models.SettingVisibilityHandler
|
|||
from cura.Settings.ExtruderManager import ExtruderManager #To get global-inherits-stack setting values from different extruders.
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
|
||||
## The per object setting visibility handler ensures that only setting
|
||||
# definitions that have a matching instance Container are returned as visible.
|
||||
class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):
|
||||
|
|
|
|||
|
|
@ -12,14 +12,18 @@ import Cura 1.0 as Cura
|
|||
Item
|
||||
{
|
||||
|
||||
// An Item whose bounds are guaranteed to be safe for overlays to be placed.
|
||||
// Defaults to parent, ie. the entire available area
|
||||
property var safeArea: parent
|
||||
|
||||
// Subtract the actionPanel from the safe area. This way the view won't draw interface elements under/over it
|
||||
Item {
|
||||
id: safeArea
|
||||
visible: false
|
||||
anchors.left: parent.left
|
||||
anchors.right: actionPanelWidget.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: actionPanelWidget.top
|
||||
Item
|
||||
{
|
||||
id: childSafeArea
|
||||
x: safeArea.x - parent.x
|
||||
y: safeArea.y - parent.y
|
||||
width: actionPanelWidget.x - x
|
||||
height: actionPanelWidget.y - y
|
||||
}
|
||||
|
||||
Loader
|
||||
|
|
@ -29,9 +33,10 @@ Item
|
|||
|
||||
source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : ""
|
||||
|
||||
onLoaded: {
|
||||
onLoaded:
|
||||
{
|
||||
if (previewMain.item.safeArea !== undefined){
|
||||
previewMain.item.safeArea = Qt.binding(function() { return safeArea });
|
||||
previewMain.item.safeArea = Qt.binding(function() { return childSafeArea });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,9 +48,13 @@ class WindowsRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
|
|||
drives = {}
|
||||
|
||||
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
|
||||
# Check possible drive letters, from A to Z
|
||||
# Check possible drive letters, from C to Z
|
||||
# Note: using ascii_uppercase because we do not want this to change with locale!
|
||||
for letter in string.ascii_uppercase:
|
||||
# Skip A and B, since those drives are typically reserved for floppy disks.
|
||||
# Those drives can theoretically be reassigned but it's safer to not check them for removable drives.
|
||||
# Windows will also behave weirdly even with some of its internal functions if you do this (e.g. search indexing doesn't search it).
|
||||
# Users that have removable drives in A or B will just have to save to file and select the drive there.
|
||||
for letter in string.ascii_uppercase[2:]:
|
||||
drive = "{0}:/".format(letter)
|
||||
|
||||
# Do we really want to skip A and B?
|
||||
|
|
|
|||
|
|
@ -155,30 +155,19 @@ Item
|
|||
}
|
||||
|
||||
onPositionChanged: parent.onHandleDragged()
|
||||
onPressed: sliderRoot.setActiveHandle(rangeHandle)
|
||||
onPressed:
|
||||
{
|
||||
sliderRoot.setActiveHandle(rangeHandle)
|
||||
sliderRoot.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
SimulationSliderLabel
|
||||
{
|
||||
id: rangleHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
x: parent.x - width - UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
target: Qt.point(sliderRoot.width, y + height / 2)
|
||||
visible: sliderRoot.activeHandle == parent
|
||||
|
||||
// custom properties
|
||||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.upperValue
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: rangeHandle.setValueManually // connect callback functions
|
||||
}
|
||||
}
|
||||
|
||||
onHeightChanged : {
|
||||
// After a height change, the pixel-position of the lower handle is out of sync with the property value
|
||||
// After a height change, the pixel-position of the handles is out of sync with the property value
|
||||
setLowerValue(lowerValue)
|
||||
setUpperValue(upperValue)
|
||||
}
|
||||
|
||||
// Upper handle
|
||||
|
|
@ -275,11 +264,12 @@ Item
|
|||
{
|
||||
id: upperHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
x: parent.x - parent.width - width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
target: Qt.point(sliderRoot.width, y + height / 2)
|
||||
visible: sliderRoot.activeHandle == parent
|
||||
height: sliderRoot.handleSize
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("narrow_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
target: Qt.point(parent.width / 2, parent.top)
|
||||
visible: sliderRoot.activeHandle == parent || sliderRoot.activeHandle == rangeHandle
|
||||
|
||||
// custom properties
|
||||
maximumValue: sliderRoot.maximumValue
|
||||
|
|
@ -384,11 +374,12 @@ Item
|
|||
{
|
||||
id: lowerHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
x: parent.x - parent.width - width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
target: Qt.point(sliderRoot.width + width, y + height / 2)
|
||||
visible: sliderRoot.activeHandle == parent
|
||||
height: sliderRoot.handleSize
|
||||
anchors.top: parent.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("narrow_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
target: Qt.point(parent.width / 2, parent.bottom)
|
||||
visible: sliderRoot.activeHandle == parent || sliderRoot.activeHandle == rangeHandle
|
||||
|
||||
// custom properties
|
||||
maximumValue: sliderRoot.maximumValue
|
||||
|
|
@ -397,4 +388,4 @@ Item
|
|||
setValue: lowerHandle.setValueManually // connect callback functions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
from UM.Application import Application
|
||||
from UM.Math.Color import Color
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ Item
|
|||
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
||||
}
|
||||
|
||||
onWidthChanged : {
|
||||
// After a width change, the pixel-position of the handle is out of sync with the property value
|
||||
setHandleValue(handleValue)
|
||||
}
|
||||
|
||||
// slider track
|
||||
Rectangle
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
|
|
@ -20,9 +19,9 @@ UM.PointingRectangle {
|
|||
property int startFrom: 1
|
||||
|
||||
target: Qt.point(parent.width, y + height / 2)
|
||||
arrowSize: UM.Theme.getSize("default_arrow").width
|
||||
arrowSize: UM.Theme.getSize("button_tooltip_arrow").height
|
||||
height: parent.height
|
||||
width: valueLabel.width + UM.Theme.getSize("default_margin").width
|
||||
width: valueLabel.width
|
||||
visible: false
|
||||
|
||||
color: UM.Theme.getColor("tool_panel_background")
|
||||
|
|
@ -40,26 +39,35 @@ UM.PointingRectangle {
|
|||
anchors.fill: parent
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: maxValueMetrics
|
||||
font: valueLabel.font
|
||||
text: maximumValue + 1 // layers are 0 based, add 1 for display value
|
||||
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: valueLabel
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
alignWhenCentered: false
|
||||
}
|
||||
|
||||
width: ((maximumValue + 1).toString().length + 1) * 10 * screenScaleFactor
|
||||
width: maxValueMetrics.width + UM.Theme.getSize("default_margin").width
|
||||
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
|
||||
horizontalAlignment: TextInput.AlignRight
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
|
||||
// key bindings, work when label is currenctly focused (active handle in LayerSlider)
|
||||
Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||
Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||
|
||||
style: TextFieldStyle {
|
||||
textColor: UM.Theme.getColor("setting_control_text")
|
||||
textColor: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("default")
|
||||
background: Item { }
|
||||
renderType: Text.NativeRendering
|
||||
background: Item { }
|
||||
}
|
||||
|
||||
onEditingFinished: {
|
||||
|
|
|
|||
|
|
@ -213,6 +213,8 @@ class SimulationView(CuraView):
|
|||
def beginRendering(self) -> None:
|
||||
scene = self.getController().getScene()
|
||||
renderer = self.getRenderer()
|
||||
if renderer is None:
|
||||
return
|
||||
|
||||
if not self._ghost_shader:
|
||||
self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
|
||||
|
|
@ -490,7 +492,11 @@ class SimulationView(CuraView):
|
|||
|
||||
# Make sure the SimulationPass is created
|
||||
layer_pass = self.getSimulationPass()
|
||||
self.getRenderer().addRenderPass(layer_pass)
|
||||
renderer = self.getRenderer()
|
||||
if renderer is None:
|
||||
return False
|
||||
|
||||
renderer.addRenderPass(layer_pass)
|
||||
|
||||
# Make sure the NozzleNode is add to the root
|
||||
nozzle = self.getNozzleNode()
|
||||
|
|
@ -509,7 +515,7 @@ class SimulationView(CuraView):
|
|||
self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
|
||||
|
||||
if not self._composite_pass:
|
||||
self._composite_pass = cast(CompositePass, self.getRenderer().getRenderPass("composite"))
|
||||
self._composite_pass = cast(CompositePass, renderer.getRenderPass("composite"))
|
||||
|
||||
self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
|
||||
self._composite_pass.getLayerBindings().append("simulationview")
|
||||
|
|
@ -525,7 +531,13 @@ class SimulationView(CuraView):
|
|||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
if self._nozzle_node:
|
||||
self._nozzle_node.setParent(None)
|
||||
self.getRenderer().removeRenderPass(self._layer_pass)
|
||||
|
||||
renderer = self.getRenderer()
|
||||
if renderer is None:
|
||||
return False
|
||||
|
||||
if self._layer_pass is not None:
|
||||
renderer.removeRenderPass(self._layer_pass)
|
||||
if self._composite_pass:
|
||||
self._composite_pass.setLayerBindings(cast(List[str], self._old_layer_bindings))
|
||||
self._composite_pass.setCompositeShader(cast(ShaderProgram, self._old_composite_shader))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ Item
|
|||
|
||||
|
||||
property bool isSimulationPlaying: false
|
||||
readonly property var layerSliderSafeYMax: safeArea.y + safeArea.height
|
||||
readonly property real layerSliderSafeYMin: safeArea.y
|
||||
readonly property real layerSliderSafeYMax: safeArea.y + safeArea.height
|
||||
readonly property real pathSliderSafeXMin: safeArea.x + playButton.width
|
||||
readonly property real pathSliderSafeXMax: safeArea.x + safeArea.width
|
||||
|
||||
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
||||
|
||||
|
|
@ -26,13 +29,21 @@ Item
|
|||
PathSlider
|
||||
{
|
||||
id: pathSlider
|
||||
|
||||
readonly property real preferredWidth: UM.Theme.getSize("slider_layerview_size").height // not a typo, should be as long as layerview slider
|
||||
readonly property real margin: UM.Theme.getSize("default_margin").width
|
||||
readonly property real pathSliderSafeWidth: pathSliderSafeXMax - pathSliderSafeXMin
|
||||
|
||||
height: UM.Theme.getSize("slider_handle").width
|
||||
width: UM.Theme.getSize("slider_layerview_size").height
|
||||
width: preferredWidth + margin * 2 < pathSliderSafeWidth ? preferredWidth : pathSliderSafeWidth - margin * 2
|
||||
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.bottomMargin: margin
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.horizontalCenterOffset: -(parent.width - pathSliderSafeXMax - pathSliderSafeXMin) / 2 // center between parent top and layerSliderSafeYMax
|
||||
|
||||
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
|
||||
|
|
@ -183,17 +194,19 @@ Item
|
|||
LayerSlider
|
||||
{
|
||||
property var preferredHeight: UM.Theme.getSize("slider_layerview_size").height
|
||||
property double heightMargin: UM.Theme.getSize("default_margin").height
|
||||
property double heightMargin: UM.Theme.getSize("default_margin").height * 3 // extra margin to accomodate layer number tooltips
|
||||
property double layerSliderSafeHeight: layerSliderSafeYMax - layerSliderSafeYMin
|
||||
|
||||
id: layerSlider
|
||||
|
||||
width: UM.Theme.getSize("slider_handle").width
|
||||
height: preferredHeight + heightMargin * 2 < layerSliderSafeYMax ? preferredHeight : layerSliderSafeYMax - heightMargin * 2
|
||||
height: preferredHeight + heightMargin * 2 < layerSliderSafeHeight ? preferredHeight : layerSliderSafeHeight - heightMargin * 2
|
||||
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
verticalCenterOffset: -(parent.height - layerSliderSafeYMax) / 2 // center between parent top and layerSliderSafeYMax
|
||||
verticalCenterOffset: -(parent.height - layerSliderSafeYMax - layerSliderSafeYMin) / 2 // center between parent top and layerSliderSafeYMax
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
bottomMargin: heightMargin
|
||||
topMargin: heightMargin
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
// Main window for the Toolbox
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import UM 1.1 as UM
|
||||
|
||||
import "./pages"
|
||||
import "./dialogs"
|
||||
import "./components"
|
||||
|
||||
Window
|
||||
{
|
||||
id: base
|
||||
|
|
@ -14,8 +20,8 @@ Window
|
|||
modality: Qt.ApplicationModal
|
||||
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint
|
||||
|
||||
width: Math.floor(720 * screenScaleFactor)
|
||||
height: Math.floor(640 * screenScaleFactor)
|
||||
width: UM.Theme.getSize("large_popup_dialog").width
|
||||
height: UM.Theme.getSize("large_popup_dialog").height
|
||||
minimumWidth: width
|
||||
maximumWidth: minimumWidth
|
||||
minimumHeight: height
|
||||
|
|
@ -29,9 +35,16 @@ Window
|
|||
Item
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
WelcomePage
|
||||
{
|
||||
visible: toolbox.viewPage === "welcome"
|
||||
}
|
||||
|
||||
ToolboxHeader
|
||||
{
|
||||
id: header
|
||||
visible: toolbox.viewPage !== "welcome"
|
||||
}
|
||||
|
||||
Item
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ Item
|
|||
width: UM.Theme.getSize("toolbox_thumbnail_small").width - UM.Theme.getSize("wide_margin").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_small").height - UM.Theme.getSize("wide_margin").width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: model.icon_url || "../images/logobot.svg"
|
||||
source: model.icon_url || "../../images/logobot.svg"
|
||||
mipmap: true
|
||||
}
|
||||
UM.RecolorImage
|
||||
|
|
@ -82,7 +82,7 @@ Item
|
|||
sourceSize.height: height
|
||||
visible: installedPackages != 0
|
||||
color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
source: "../images/installed_check.svg"
|
||||
source: "../../images/installed_check.svg"
|
||||
}
|
||||
}
|
||||
Item
|
||||
|
|
@ -23,7 +23,7 @@ Rectangle
|
|||
height: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_large").height - 4 * UM.Theme.getSize("default_margin").height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: model.icon_url || "../images/logobot.svg"
|
||||
source: model.icon_url || "../../images/logobot.svg"
|
||||
mipmap: true
|
||||
anchors
|
||||
{
|
||||
|
|
@ -62,7 +62,7 @@ Rectangle
|
|||
}
|
||||
visible: installedPackages != 0
|
||||
color: (installedPackages >= packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
|
||||
source: "../images/installed_check.svg"
|
||||
source: "../../images/installed_check.svg"
|
||||
}
|
||||
|
||||
SmallRatingWidget
|
||||
|
|
@ -14,7 +14,7 @@ import Cura 1.0 as Cura
|
|||
|
||||
UM.Dialog
|
||||
{
|
||||
// This dialog asks the user whether he/she wants to open a project file as a project or import models.
|
||||
// This dialog asks the user to confirm he/she wants to uninstall materials/pprofiles which are currently in use
|
||||
id: base
|
||||
|
||||
title: catalog.i18nc("@title:window", "Confirm uninstall") + toolbox.pluginToUninstall
|
||||
|
|
@ -6,6 +6,8 @@ import QtQuick.Controls 1.4
|
|||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
import "../components"
|
||||
|
||||
Item
|
||||
{
|
||||
id: page
|
||||
|
|
@ -31,7 +33,7 @@ Item
|
|||
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: details.icon_url || "../images/logobot.svg"
|
||||
source: details.icon_url || "../../images/logobot.svg"
|
||||
mipmap: true
|
||||
anchors
|
||||
{
|
||||
|
|
@ -8,6 +8,8 @@ import UM 1.1 as UM
|
|||
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
import "../components"
|
||||
|
||||
Item
|
||||
{
|
||||
id: page
|
||||
|
|
@ -44,7 +46,7 @@ Item
|
|||
{
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: details === null ? "" : (details.icon_url || "../images/logobot.svg")
|
||||
source: details === null ? "" : (details.icon_url || "../../images/logobot.svg")
|
||||
mipmap: true
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import QtQuick 2.10
|
|||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
import "../components"
|
||||
|
||||
ScrollView
|
||||
{
|
||||
clip: true
|
||||
|
|
@ -6,6 +6,8 @@ import QtQuick.Controls 2.3
|
|||
|
||||
import UM 1.1 as UM
|
||||
|
||||
import "../components"
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: page
|
||||
53
plugins/Toolbox/resources/qml/pages/WelcomePage.qml
Normal file
53
plugins/Toolbox/resources/qml/pages/WelcomePage.qml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
Column
|
||||
{
|
||||
id: welcomePage
|
||||
spacing: UM.Theme.getSize("wide_margin").height
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
Image
|
||||
{
|
||||
id: profileImage
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../images/logobot.svg"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.round(parent.width / 4)
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: welcomeTextLabel
|
||||
text: catalog.i18nc("@description", "Get plugins and materials verified by Ultimaker")
|
||||
width: Math.round(parent.width / 2)
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Label.WordWrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: loginButton
|
||||
width: UM.Theme.getSize("account_button").width
|
||||
height: UM.Theme.getSize("account_button").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: catalog.i18nc("@button", "Sign in")
|
||||
onClicked: Cura.API.account.login()
|
||||
fixedWidthMode: true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import re
|
||||
from typing import Dict
|
||||
from PyQt5.QtCore import Qt, pyqtProperty
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
## Model that holds supported configurations (for material/quality packages).
|
||||
class ConfigsModel(ListModel):
|
||||
def __init__(self, parent = None):
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class Toolbox(QObject, Extension):
|
|||
self._download_progress = 0 # type: float
|
||||
self._is_downloading = False # type: bool
|
||||
self._network_manager = None # type: Optional[QNetworkAccessManager]
|
||||
self._request_headers = [] # type: List[Tuple[bytes, bytes]]
|
||||
self._request_headers = [] # type: List[Tuple[bytes, bytes]]
|
||||
self._updateRequestHeader()
|
||||
|
||||
self._request_urls = {} # type: Dict[str, QUrl]
|
||||
|
|
@ -59,13 +59,15 @@ class Toolbox(QObject, Extension):
|
|||
# The responses as given by the server parsed to a list.
|
||||
self._server_response_data = {
|
||||
"authors": [],
|
||||
"packages": []
|
||||
"packages": [],
|
||||
"updates": [],
|
||||
} # type: Dict[str, List[Any]]
|
||||
|
||||
# Models:
|
||||
self._models = {
|
||||
"authors": AuthorsModel(self),
|
||||
"packages": PackagesModel(self),
|
||||
"updates": PackagesModel(self),
|
||||
} # type: Dict[str, Union[AuthorsModel, PackagesModel]]
|
||||
|
||||
self._plugins_showcase_model = PackagesModel(self)
|
||||
|
|
@ -86,7 +88,7 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
# View page defines which type of page layout to use. For example,
|
||||
# possible values include "overview", "detail" or "author".
|
||||
self._view_page = "loading" # type: str
|
||||
self._view_page = "welcome" # type: str
|
||||
|
||||
# Active package refers to which package is currently being downloaded,
|
||||
# installed, or otherwise modified.
|
||||
|
|
@ -105,7 +107,6 @@ class Toolbox(QObject, Extension):
|
|||
self._restart_dialog_message = "" # type: str
|
||||
|
||||
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader)
|
||||
self._application.getCuraAPI().account.accessTokenChanged.connect(self._updateRequestHeader)
|
||||
|
||||
# Signals:
|
||||
|
|
@ -126,6 +127,16 @@ class Toolbox(QObject, Extension):
|
|||
showLicenseDialog = pyqtSignal()
|
||||
uninstallVariablesChanged = pyqtSignal()
|
||||
|
||||
## Go back to the start state (welcome screen or loading if no login required)
|
||||
def _restart(self):
|
||||
self._updateRequestHeader()
|
||||
# For an Essentials build, login is mandatory
|
||||
if not self._application.getCuraAPI().account.isLoggedIn and ApplicationMetadata.IsEnterpriseVersion:
|
||||
self.setViewPage("welcome")
|
||||
else:
|
||||
self.setViewPage("loading")
|
||||
self._fetchPackageData()
|
||||
|
||||
def _updateRequestHeader(self):
|
||||
self._request_headers = [
|
||||
(b"User-Agent",
|
||||
|
|
@ -186,18 +197,27 @@ class Toolbox(QObject, Extension):
|
|||
cloud_api_version = self._cloud_api_version,
|
||||
sdk_version = self._sdk_version
|
||||
)
|
||||
|
||||
# We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc.
|
||||
installed_package_ids_with_versions = [":".join(items) for items in
|
||||
self._package_manager.getAllInstalledPackageIdsAndVersions()]
|
||||
installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions)
|
||||
|
||||
self._request_urls = {
|
||||
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
|
||||
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url))
|
||||
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
|
||||
"updates": QUrl("{base_url}/packages/package-updates?installed_packages={query}".format(
|
||||
base_url = self._api_url, query = installed_packages_query))
|
||||
}
|
||||
|
||||
# Request the latest and greatest!
|
||||
self._fetchPackageData()
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._restart)
|
||||
|
||||
def _fetchPackageData(self):
|
||||
# Create the network manager:
|
||||
# This was formerly its own function but really had no reason to be as
|
||||
# it was never called more than once ever.
|
||||
# On boot we check which packages have updates.
|
||||
if CuraApplication.getInstance().getPreferences().getValue("info/automatic_update_check") and len(installed_package_ids_with_versions) > 0:
|
||||
# Request the latest and greatest!
|
||||
self._fetchPackageUpdates()
|
||||
|
||||
def _prepareNetworkManager(self):
|
||||
if self._network_manager is not None:
|
||||
self._network_manager.finished.disconnect(self._onRequestFinished)
|
||||
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged)
|
||||
|
|
@ -205,16 +225,21 @@ class Toolbox(QObject, Extension):
|
|||
self._network_manager.finished.connect(self._onRequestFinished)
|
||||
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged)
|
||||
|
||||
def _fetchPackageUpdates(self):
|
||||
self._prepareNetworkManager()
|
||||
self._makeRequestByType("updates")
|
||||
|
||||
def _fetchPackageData(self):
|
||||
self._prepareNetworkManager()
|
||||
# Make remote requests:
|
||||
self._makeRequestByType("packages")
|
||||
self._makeRequestByType("authors")
|
||||
|
||||
# Gather installed packages:
|
||||
self._updateInstalledModels()
|
||||
|
||||
# Displays the toolbox
|
||||
@pyqtSlot()
|
||||
def browsePackages(self) -> None:
|
||||
self._fetchPackageData()
|
||||
def launch(self) -> None:
|
||||
|
||||
if not self._dialog:
|
||||
self._dialog = self._createDialog("Toolbox.qml")
|
||||
|
|
@ -223,6 +248,8 @@ class Toolbox(QObject, Extension):
|
|||
Logger.log("e", "Unexpected error trying to create the 'Marketplace' dialog.")
|
||||
return
|
||||
|
||||
self._restart()
|
||||
|
||||
self._dialog.show()
|
||||
|
||||
# Apply enabled/disabled state to installed plugins
|
||||
|
|
@ -234,7 +261,7 @@ class Toolbox(QObject, Extension):
|
|||
if not plugin_path:
|
||||
return None
|
||||
path = os.path.join(plugin_path, "resources", "qml", qml_name)
|
||||
|
||||
|
||||
dialog = self._application.createQmlComponent(path, {"toolbox": self})
|
||||
if not dialog:
|
||||
raise Exception("Failed to create Marketplace dialog")
|
||||
|
|
@ -328,7 +355,7 @@ class Toolbox(QObject, Extension):
|
|||
self._package_used_qualities = package_used_qualities
|
||||
# Ask change to default material / profile
|
||||
if self._confirm_reset_dialog is None:
|
||||
self._confirm_reset_dialog = self._createDialog("ToolboxConfirmUninstallResetDialog.qml")
|
||||
self._confirm_reset_dialog = self._createDialog("dialogs/ToolboxConfirmUninstallResetDialog.qml")
|
||||
self.uninstallVariablesChanged.emit()
|
||||
if self._confirm_reset_dialog is None:
|
||||
Logger.log("e", "ToolboxConfirmUninstallResetDialog should have been initialized, but it is not. Not showing dialog and not uninstalling package.")
|
||||
|
|
@ -618,7 +645,7 @@ class Toolbox(QObject, Extension):
|
|||
if not self._models[response_type]:
|
||||
Logger.log("e", "Could not find the %s model.", response_type)
|
||||
break
|
||||
|
||||
|
||||
self._server_response_data[response_type] = json_data["data"]
|
||||
self._models[response_type].setMetadata(self._server_response_data[response_type])
|
||||
|
||||
|
|
@ -630,6 +657,10 @@ class Toolbox(QObject, Extension):
|
|||
elif response_type == "authors":
|
||||
self._models[response_type].setFilter({"package_types": "material"})
|
||||
self._models[response_type].setFilter({"tags": "generic"})
|
||||
elif response_type == "updates":
|
||||
# Tell the package manager that there's a new set of updates available.
|
||||
packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]])
|
||||
self._package_manager.setPackagesWithUpdate(packages)
|
||||
|
||||
self.metadataChanged.emit()
|
||||
|
||||
|
|
@ -660,7 +691,7 @@ class Toolbox(QObject, Extension):
|
|||
self.setIsDownloading(False)
|
||||
self._download_reply = cast(QNetworkReply, self._download_reply)
|
||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||
|
||||
|
||||
# Check if the download was sucessfull
|
||||
if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from cura.Settings.GlobalStack import GlobalStack
|
|||
from .CloudApiClient import CloudApiClient
|
||||
from .CloudOutputDevice import CloudOutputDevice
|
||||
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
|
||||
from ..Messages.CloudPrinterDetectedMessage import CloudPrinterDetectedMessage
|
||||
|
||||
|
||||
## The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
|
||||
|
|
@ -111,7 +110,6 @@ class CloudOutputDeviceManager:
|
|||
)
|
||||
self._remote_clusters[device.getId()] = device
|
||||
self.discoveredDevicesChanged.emit()
|
||||
self._checkIfNewClusterWasAdded(device.clusterData.cluster_id)
|
||||
self._connectToActiveMachine()
|
||||
|
||||
def _onDiscoveredDeviceUpdated(self, cluster_data: CloudClusterResponse) -> None:
|
||||
|
|
@ -183,11 +181,4 @@ class CloudOutputDeviceManager:
|
|||
|
||||
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
|
||||
if device.key not in output_device_manager.getOutputDeviceIds():
|
||||
output_device_manager.addOutputDevice(device)
|
||||
|
||||
## Checks if Cura has a machine stack (printer) for the given cluster ID and shows a message if it hasn't.
|
||||
def _checkIfNewClusterWasAdded(self, cluster_id: str) -> None:
|
||||
container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||
cloud_machines = container_registry.findContainersMetadata(**{self.META_CLUSTER_ID: "*"}) # all cloud machines
|
||||
if not any(machine[self.META_CLUSTER_ID] == cluster_id for machine in cloud_machines):
|
||||
CloudPrinterDetectedMessage().show()
|
||||
output_device_manager.addOutputDevice(device)
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from UM import i18nCatalog
|
||||
from UM.Message import Message
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
I18N_CATALOG = i18nCatalog("cura")
|
||||
|
||||
|
||||
## Message shown when a new printer was added to your account but not yet in Cura.
|
||||
class CloudPrinterDetectedMessage(Message):
|
||||
|
||||
# Singleton used to prevent duplicate messages of this type at the same time.
|
||||
__is_visible = False
|
||||
|
||||
# Store in preferences to hide this message in the future.
|
||||
_preference_key = "cloud/block_new_printers_popup"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
title=I18N_CATALOG.i18nc("@info:title", "New cloud printers found"),
|
||||
text=I18N_CATALOG.i18nc("@info:message", "New printers have been found connected to your account, "
|
||||
"you can find them in your list of discovered printers."),
|
||||
lifetime=0,
|
||||
dismissable=True,
|
||||
option_state=False,
|
||||
option_text=I18N_CATALOG.i18nc("@info:option_text", "Do not show this message again")
|
||||
)
|
||||
self.optionToggled.connect(self._onDontAskMeAgain)
|
||||
CuraApplication.getInstance().getPreferences().addPreference(self._preference_key, False)
|
||||
|
||||
def show(self) -> None:
|
||||
if CuraApplication.getInstance().getPreferences().getValue(self._preference_key):
|
||||
return
|
||||
if CloudPrinterDetectedMessage.__is_visible:
|
||||
return
|
||||
super().show()
|
||||
CloudPrinterDetectedMessage.__is_visible = True
|
||||
|
||||
def hide(self, send_signal = True) -> None:
|
||||
super().hide(send_signal)
|
||||
CloudPrinterDetectedMessage.__is_visible = False
|
||||
|
||||
def _onDontAskMeAgain(self, checked: bool) -> None:
|
||||
CuraApplication.getInstance().getPreferences().setValue(self._preference_key, checked)
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import List, Optional, Union, Dict, Any
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
|
||||
|
||||
from .ClusterBuildPlate import ClusterBuildPlate
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class ZeroConfClient:
|
|||
Logger.logException("e", "Failed to create zeroconf instance.")
|
||||
return
|
||||
|
||||
self._service_changed_request_thread = Thread(target = self._handleOnServiceChangedRequests, daemon = True)
|
||||
self._service_changed_request_thread = Thread(target = self._handleOnServiceChangedRequests, daemon = True, name = "ZeroConfServiceChangedThread")
|
||||
self._service_changed_request_thread.start()
|
||||
self._zero_conf_browser = ServiceBrowser(self._zero_conf, self.ZERO_CONF_NAME, [self._queueService])
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._all_baud_rates = [115200, 250000, 500000, 230400, 57600, 38400, 19200, 9600]
|
||||
|
||||
# Instead of using a timer, we really need the update to be as a thread, as reading from serial can block.
|
||||
self._update_thread = Thread(target = self._update, daemon = True)
|
||||
self._update_thread = Thread(target = self._update, daemon = True, name = "USBPrinterUpdate")
|
||||
|
||||
self._last_temperature_request = None # type: Optional[int]
|
||||
self._firmware_idle_count = 0
|
||||
|
|
@ -212,7 +212,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._serial.close()
|
||||
|
||||
# Re-create the thread so it can be started again later.
|
||||
self._update_thread = Thread(target=self._update, daemon=True)
|
||||
self._update_thread = Thread(target=self._update, daemon=True, name = "USBPrinterUpdate")
|
||||
self._serial = None
|
||||
|
||||
## Send a command to printer.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import io #To write config files to strings as if they were files.
|
|||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import UM.VersionUpgrade
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
||||
## Creates a new profile instance by parsing a serialised profile in version 1
|
||||
# of the file format.
|
||||
|
|
@ -20,6 +20,7 @@ def importFrom(serialised: str, filename: str) -> Optional["Profile"]:
|
|||
except (configparser.Error, UM.VersionUpgrade.FormatException, UM.VersionUpgrade.InvalidVersionException):
|
||||
return None
|
||||
|
||||
|
||||
## A representation of a profile used as intermediary form for conversion from
|
||||
# one format to the other.
|
||||
class Profile:
|
||||
|
|
|
|||
|
|
@ -325,7 +325,9 @@ class VersionUpgrade41to42(VersionUpgrade):
|
|||
material_id = parser["containers"]["3"]
|
||||
old_quality_id = parser["containers"]["2"]
|
||||
if material_id in _creality_quality_per_material and old_quality_id in _creality_quality_per_material[material_id]:
|
||||
parser["containers"]["2"] = _creality_quality_per_material[material_id][old_quality_id]
|
||||
if definition_id == "creality_cr10_extruder_0": # We can't disambiguate between Creality CR-10 and Creality-CR10S since they share the same extruder definition. Have to go by the name.
|
||||
if "cr-10s" in parser["metadata"].get("machine", "Creality CR-10").lower(): # Not perfect, since the user can change this name :(
|
||||
parser["containers"]["2"] = _creality_quality_per_material[material_id][old_quality_id]
|
||||
|
||||
stack_copy = {} # type: Dict[str, str] # Make a copy so that we don't modify the dict we're iterating over.
|
||||
stack_copy.update(parser["containers"])
|
||||
|
|
|
|||
|
|
@ -66,6 +66,13 @@ class VersionUpgrade43to44(VersionUpgrade):
|
|||
# Alternate skin rotation should be translated to top/bottom line directions.
|
||||
if "skin_alternate_rotation" in parser["values"] and parseBool(parser["values"]["skin_alternate_rotation"]):
|
||||
parser["values"]["skin_angles"] = "[45, 135, 0, 90]"
|
||||
# Unit of adaptive layers topography size changed.
|
||||
if "adaptive_layer_height_threshold" in parser["values"]:
|
||||
val = parser["values"]["adaptive_layer_height_threshold"]
|
||||
if val.startswith("="):
|
||||
val = val[1:]
|
||||
val = "=({val}) / 1000".format(val = val) # Convert microns to millimetres. Works even if the profile contained a formula.
|
||||
parser["values"]["adaptive_layer_height_threshold"] = val
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ definition = creality_cr10s
|
|||
|
||||
[metadata]
|
||||
type = definition_changes
|
||||
setting_version = 10
|
||||
setting_version = 11
|
||||
|
||||
[values]
|
||||
%s
|
||||
|
|
|
|||
|
|
@ -720,6 +720,8 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
new_hotend_material._dirty = False
|
||||
|
||||
if is_new_material:
|
||||
if ContainerRegistry.getInstance().isReadOnly(self.getId()):
|
||||
ContainerRegistry.getInstance().setExplicitReadOnly(new_hotend_material.getId())
|
||||
containers_to_add.append(new_hotend_material)
|
||||
|
||||
# there is only one ID for a machine. Once we have reached here, it means we have already found
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue