Merge remote-tracking branch 'origin/3.5' into cura_connect_improvements_3.5

This commit is contained in:
Simon Edwards 2018-09-25 16:59:33 +02:00
commit e99b376374
97 changed files with 22454 additions and 15235 deletions

View file

@ -225,7 +225,7 @@ class ThreeMFReader(MeshReader):
except Exception:
Logger.logException("e", "An exception occurred in 3mf reader.")
return []
return None
return result

View file

@ -1,3 +1,103 @@
[3.5.0]
*Monitor page
The monitor page of Ultimaker Cura has been remodeled for better consistency with the Cura Connect Print jobs interface. This means less switching between interfaces, and more control from within Ultimaker Cura.
*Open recent projects
Project files can now be found in the Open Recent menu.
*New tool hotkeys
New hotkeys have been assigned for quick toggling between the translate (T), scale (S), rotate (R) and mirror (M) tools.
*Project files use 3MF only
A 3MF extension is now used for project files. The .curaproject extension is no longer used.
*Camera maximum zoom
The maximum zoom has been adjusted to scale with the size of the selected printer. This fixes third-party printers with huge build volumes to be correctly visible.
*Corrected width of layer number box
The layer number indicator in the layer view now displays numbers above 999 correctly.
*Materials preferences
This screen has been redesigned to improve user experience. Materials can now be set as a favorites, so they can be easily accessed in the material selection panel at the top-right of the screen.
*Installed packages checkmark
Packages that are already installed in the Toolbox are now have a checkmark for easy reference.
*Mac OSX save dialog
The save dialog has been restored to its native behavior and bugs have been fixed.
*Removed .gz extension
Saving compressed g-code files from the save dialog has been removed because of incompatibility with MacOS. If sending jobs over Wi-Fi, g-code is still compressed.
*Updates to Chinese translations
Improved and updated Chinese translations. Contributed by MarmaladeForMeat.
*Save project
Saving the project no longer triggers the project to reslice.
*File menu
The Save option in the file menu now saves project files. The export option now saves other types of files, such as STL.
*Improved processing of overhang walls
Overhang walls are detected and printed with different speeds. It will not start a perimeter on an overhanging wall. The quality of overhanging walls may be improved by printing those at a different speed. Contributed by smartavionics.
*Prime tower reliability
The prime tower has been improved for better reliability. This is especially useful when printing with two materials that do not adhere well.
*Support infill line direction
The support infill lines can now be rotated to increase the supporting capabilities and reduce artifacts on the model. This setting rotates existing patterns, like triangle support infill. Contributed by fieldOfView.
*Minimum polygon circumference
Polygons in sliced layers that have a circumference smaller than the setting value will be filtered out. Lower values lead to higher resolution meshes at the cost of increased slicing time. This setting is ideal for very tiny prints with a lot of detail, or for SLA printers. Contributed by cubiq.
*Initial layer support line distance
This setting enables the user to reduce or increase the density of the support initial layer in order to increase or reduce adhesion to the build plate and the overall strength.
*Extra infill wall line count
Adds extra walls around infill. Contributed by BagelOrb.
*Multiply infill
Creates multiple infill lines on the same pattern for sturdier infill. Contributed by BagelOrb.
*Connected infill polygons
Connecting infill lines now also works with concentric and cross infill patterns. The benefit would be stronger infill and more consistent material flow/saving retractions. Contributed by BagelOrb.
*Fan speed override
New setting to modify the fan speed of supported areas. This setting can be found in Support settings > Fan Speed Override when support is enabled. Contributed by smartavionics.
*Minimum wall flow
New setting to define a minimum flow for thin printed walls. Contributed by smartavionics.
*Custom support plugin
A tool downloadable from the toolbox, similar to the support blocker, that adds cubes of support to the model manually by clicking parts of it. Contributed by Lokster.
*Quickly toggle autoslicing
Adds a pause/play button to the progress bar to quickly toggle autoslicing. Contributed by fieldOfview.
*Cura-DuetRRFPlugin
Adds output devices for a Duet RepRapFirmware printer: "Print", "Simulate", and "Upload". Contributed by Kriechi.
*Dremel 3D20
This plugin adds the Dremel printer to Ultimaker Cura. Contributed by Kriechi.
*Bug fixes
- Removed extra M109 commands. Older versions would generate superfluous M109 commands. This has been fixed for better temperature stability when printing.
- Fixed minor mesh handling bugs. A few combinations of modifier meshes now lead to expected behavior.
- Removed unnecessary travels. Connected infill lines are now always printed completely connected, without unnecessary travel moves.
- Removed concentric 3D infill. This infill type has been removed due to lack of reliability.
- Extra skin wall count. Fixed an issue that caused extra print moves with this setting enabled.
- Concentric skin. Small gaps in concentric skin are now filled correctly.
- Order of printed models. The order of a large batch of printed models is now more consistent, instead of random.
*Third party printers
- TiZYX
- Winbo
- Tevo Tornado
- Creality CR-10S
- Wanhao Duplicator
- Deltacomb (update)
- Dacoma (update)
[3.4.1]
*Bug fixes
- Fixed an issue that would occasionally cause an unnecessary extra skin wall to be printed, which increased print time.

View file

@ -891,7 +891,7 @@ Cura.MachineAction
{
id: machineHeadPolygonProvider
containerStackId: base.acthiveMachineId
containerStackId: base.activeMachineId
key: "machine_head_with_fans_polygon"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex

View file

@ -9,7 +9,8 @@ import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item {
Item
{
id: sliderRoot
// handle properties
@ -39,40 +40,49 @@ Item {
property real lowerValue: minimumValue
property bool layersVisible: true
property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
function getUpperValueFromSliderHandle() {
function getUpperValueFromSliderHandle()
{
return upperHandle.getValue()
}
function setUpperValue(value) {
function setUpperValue(value)
{
upperHandle.setValue(value)
updateRangeHandle()
}
function getLowerValueFromSliderHandle() {
function getLowerValueFromSliderHandle()
{
return lowerHandle.getValue()
}
function setLowerValue(value) {
function setLowerValue(value)
{
lowerHandle.setValue(value)
updateRangeHandle()
}
function updateRangeHandle() {
function updateRangeHandle()
{
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
}
// set the active handle to show only one label at a time
function setActiveHandle(handle) {
function setActiveHandle(handle)
{
activeHandle = handle
}
function normalizeValue(value) {
function normalizeValue(value)
{
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
// slider track
Rectangle {
Rectangle
{
id: track
width: sliderRoot.trackThickness
@ -86,7 +96,8 @@ Item {
}
// Range handle
Item {
Item
{
id: rangeHandle
y: upperHandle.y + upperHandle.height
@ -96,7 +107,9 @@ Item {
visible: sliderRoot.layersVisible
// set the new value when dragging
function onHandleDragged () {
function onHandleDragged()
{
sliderRoot.manuallyChanged = true
upperHandle.y = y - upperHandle.height
lowerHandle.y = y + height
@ -109,7 +122,14 @@ Item {
UM.SimulationView.setMinimumLayer(lowerValue)
}
function setValue (value) {
function setValueManually(value)
{
sliderRoot.manuallyChanged = true
upperHandle.setValue(value)
}
function setValue(value)
{
var range = sliderRoot.upperValue - sliderRoot.lowerValue
value = Math.min(value, sliderRoot.maximumValue)
value = Math.max(value, sliderRoot.minimumValue + range)
@ -118,17 +138,20 @@ Item {
UM.SimulationView.setMinimumLayer(value - range)
}
Rectangle {
Rectangle
{
width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
height: parent.height + sliderRoot.handleSize
anchors.centerIn: parent
color: sliderRoot.rangeHandleColor
}
MouseArea {
MouseArea
{
anchors.fill: parent
drag {
drag
{
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height
@ -139,7 +162,8 @@ Item {
onPressed: sliderRoot.setActiveHandle(rangeHandle)
}
SimulationSliderLabel {
SimulationSliderLabel
{
id: rangleHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -152,12 +176,13 @@ Item {
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.SimulationView.busy
setValue: rangeHandle.setValue // connect callback functions
setValue: rangeHandle.setValueManually // connect callback functions
}
}
// Upper handle
Rectangle {
Rectangle
{
id: upperHandle
y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
@ -168,10 +193,13 @@ Item {
color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
visible: sliderRoot.layersVisible
function onHandleDragged () {
function onHandleDragged()
{
sliderRoot.manuallyChanged = true
// don't allow the lower handle to be heigher than the upper handle
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize)
{
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
}
@ -183,15 +211,23 @@ Item {
}
// get the upper value based on the slider position
function getValue () {
function getValue()
{
var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
function setValueManually(value)
{
sliderRoot.manuallyChanged = true
upperHandle.setValue(value)
}
// set the slider position based on the upper value
function setValue (value) {
function setValue(value)
{
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
@ -209,10 +245,12 @@ Item {
Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
MouseArea {
MouseArea
{
anchors.fill: parent
drag {
drag
{
target: parent
axis: Drag.YAxis
minimumY: 0
@ -220,13 +258,15 @@ Item {
}
onPositionChanged: parent.onHandleDragged()
onPressed: {
onPressed:
{
sliderRoot.setActiveHandle(upperHandle)
upperHandleLabel.forceActiveFocus()
}
}
SimulationSliderLabel {
SimulationSliderLabel
{
id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -239,12 +279,13 @@ Item {
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.SimulationView.busy
setValue: upperHandle.setValue // connect callback functions
setValue: upperHandle.setValueManually // connect callback functions
}
}
// Lower handle
Rectangle {
Rectangle
{
id: lowerHandle
y: sliderRoot.height - sliderRoot.handleSize
@ -256,10 +297,13 @@ Item {
visible: sliderRoot.layersVisible
function onHandleDragged () {
function onHandleDragged()
{
sliderRoot.manuallyChanged = true
// don't allow the upper handle to be lower than the lower handle
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize)
{
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
}
@ -271,15 +315,24 @@ Item {
}
// get the lower value from the current slider position
function getValue () {
function getValue()
{
var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
function setValueManually(value)
{
sliderRoot.manuallyChanged = true
lowerHandle.setValue(value)
}
// set the slider position based on the lower value
function setValue (value) {
function setValue(value)
{
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
@ -297,10 +350,12 @@ Item {
Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
MouseArea {
MouseArea
{
anchors.fill: parent
drag {
drag
{
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
@ -308,13 +363,15 @@ Item {
}
onPositionChanged: parent.onHandleDragged()
onPressed: {
onPressed:
{
sliderRoot.setActiveHandle(lowerHandle)
lowerHandleLabel.forceActiveFocus()
}
}
SimulationSliderLabel {
SimulationSliderLabel
{
id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -327,7 +384,7 @@ Item {
maximumValue: sliderRoot.maximumValue
value: sliderRoot.lowerValue
busy: UM.SimulationView.busy
setValue: lowerHandle.setValue // connect callback functions
setValue: lowerHandle.setValueManually // connect callback functions
}
}
}
}

View file

@ -9,7 +9,8 @@ import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item {
Item
{
id: sliderRoot
// handle properties
@ -34,26 +35,32 @@ Item {
property real handleValue: maximumValue
property bool pathsVisible: true
property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
function getHandleValueFromSliderHandle () {
function getHandleValueFromSliderHandle()
{
return handle.getValue()
}
function setHandleValue (value) {
function setHandleValue(value)
{
handle.setValue(value)
updateRangeHandle()
}
function updateRangeHandle () {
function updateRangeHandle()
{
rangeHandle.width = handle.x - sliderRoot.handleSize
}
function normalizeValue(value) {
function normalizeValue(value)
{
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
// slider track
Rectangle {
Rectangle
{
id: track
width: sliderRoot.width - sliderRoot.handleSize
@ -67,7 +74,8 @@ Item {
}
// Progress indicator
Item {
Item
{
id: rangeHandle
x: handle.width
@ -76,7 +84,8 @@ Item {
anchors.verticalCenter: sliderRoot.verticalCenter
visible: sliderRoot.pathsVisible
Rectangle {
Rectangle
{
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
width: parent.width + sliderRoot.handleSize
anchors.centerIn: parent
@ -85,7 +94,8 @@ Item {
}
// Handle
Rectangle {
Rectangle
{
id: handle
x: sliderRoot.handleSize
@ -96,7 +106,9 @@ Item {
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
visible: sliderRoot.pathsVisible
function onHandleDragged () {
function onHandleDragged()
{
sliderRoot.manuallyChanged = true
// update the range handle
sliderRoot.updateRangeHandle()
@ -106,15 +118,23 @@ Item {
}
// get the value based on the slider position
function getValue () {
function getValue()
{
var result = x / (sliderRoot.width - sliderRoot.handleSize)
result = result * sliderRoot.maximumValue
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
function setValueManually(value)
{
sliderRoot.manuallyChanged = true
handle.setValue(value)
}
// set the slider position based on the value
function setValue (value) {
function setValue(value)
{
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
@ -132,23 +152,23 @@ Item {
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
MouseArea {
MouseArea
{
anchors.fill: parent
drag {
drag
{
target: parent
axis: Drag.XAxis
minimumX: 0
maximumX: sliderRoot.width - sliderRoot.handleSize
}
onPressed: {
handleLabel.forceActiveFocus()
}
onPressed: handleLabel.forceActiveFocus()
onPositionChanged: parent.onHandleDragged()
}
SimulationSliderLabel {
SimulationSliderLabel
{
id: handleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -162,7 +182,7 @@ Item {
maximumValue: sliderRoot.maximumValue
value: sliderRoot.handleValue
busy: UM.SimulationView.busy
setValue: handle.setValue // connect callback functions
setValue: handle.setValueManually // connect callback functions
}
}
}

View file

@ -623,7 +623,15 @@ Item
{
target: UM.SimulationView
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
onCurrentPathChanged:
{
// Only pause the simulation when the layer was changed manually, not when the simulation is running
if (pathSlider.manuallyChanged)
{
playButton.pauseSimulation()
}
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
// make sure the slider handlers show the correct value after switching views
@ -668,7 +676,15 @@ Item
target: UM.SimulationView
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
onCurrentLayerChanged:
{
// Only pause the simulation when the layer was changed manually, not when the simulation is running
if (layerSlider.manuallyChanged)
{
playButton.pauseSimulation()
}
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
// make sure the slider handlers show the correct value after switching views
@ -716,6 +732,8 @@ Item
iconSource = "./resources/simulation_resume.svg"
simulationTimer.stop()
status = 0
layerSlider.manuallyChanged = true
pathSlider.manuallyChanged = true
}
function resumeSimulation()
@ -723,6 +741,8 @@ Item
UM.SimulationView.setSimulationRunning(true)
iconSource = "./resources/simulation_pause.svg"
simulationTimer.start()
layerSlider.manuallyChanged = false
pathSlider.manuallyChanged = false
}
}
@ -773,6 +793,8 @@ Item
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
// correctly, because part of the logic is in this trigger function.
playButton.status = 1
}
}

View file

@ -5,6 +5,7 @@ import json
import os
import platform
import time
from typing import cast, Optional, Set
from PyQt5.QtCore import pyqtSlot, QObject
@ -16,7 +17,7 @@ from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.Qt.Duration import DurationFormat
from typing import cast, Optional
from .SliceInfoJob import SliceInfoJob
@ -95,13 +96,29 @@ class SliceInfo(QObject, Extension):
def setSendSliceInfo(self, enabled: bool):
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
def _getUserModifiedSettingKeys(self) -> list:
from cura.CuraApplication import CuraApplication
application = cast(CuraApplication, Application.getInstance())
machine_manager = application.getMachineManager()
global_stack = machine_manager.activeMachine
user_modified_setting_keys = set() # type: Set[str]
for stack in [global_stack] + list(global_stack.extruders.values()):
# Get all settings in user_changes and quality_changes
all_keys = stack.userChanges.getAllKeys() | stack.qualityChanges.getAllKeys()
user_modified_setting_keys |= all_keys
return list(sorted(user_modified_setting_keys))
def _onWriteStarted(self, output_device):
try:
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
Logger.log("d", "'info/send_slice_info' is turned off.")
return # Do nothing, user does not want to send data
application = Application.getInstance()
from cura.CuraApplication import CuraApplication
application = cast(CuraApplication, Application.getInstance())
machine_manager = application.getMachineManager()
print_information = application.getPrintInformation()
@ -164,6 +181,8 @@ class SliceInfo(QObject, Extension):
data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type")
data["user_modified_setting_keys"] = self._getUserModifiedSettingKeys()
data["models"] = []
# Listing all files placed on the build plate
for node in DepthFirstIterator(application.getController().getScene().getRoot()):

View file

@ -56,6 +56,7 @@
}
],
"quality_profile": "fast",
"user_modified_setting_keys": ["layer_height", "wall_line_width", "infill_sparse_density"],
"models": [
{
"hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070",

View file

@ -34,6 +34,7 @@ Item
}
}
/* // NOTE: Remember to re-enable for v3.6!
ToolboxTabButton
{
text: catalog.i18nc("@title:tab", "Materials")
@ -46,6 +47,7 @@ Item
toolbox.viewPage = "overview"
}
}
*/
}
ToolboxTabButton
{

View file

@ -1,12 +1,11 @@
# Copyright (c) 2018 Ultimaker B.V.
# Toolbox is released under the terms of the LGPLv3 or higher.
from typing import Dict, Optional, Union, Any, cast
import json
import os
import tempfile
import platform
from typing import cast, List
from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
@ -20,9 +19,13 @@ from UM.Version import Version
import cura
from cura.CuraApplication import CuraApplication
from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
i18n_catalog = i18nCatalog("cura")
@ -34,19 +37,19 @@ class Toolbox(QObject, Extension):
def __init__(self, application: CuraApplication) -> None:
super().__init__()
self._application = application #type: CuraApplication
self._application = application # type: CuraApplication
self._sdk_version = None # type: Optional[int]
self._cloud_api_version = None # type: Optional[int]
self._cloud_api_root = None # type: Optional[str]
self._api_url = None # type: Optional[str]
self._sdk_version = None # type: Optional[Union[str, int]]
self._cloud_api_version = None # type: Optional[int]
self._cloud_api_root = None # type: Optional[str]
self._api_url = None # type: Optional[str]
# Network:
self._download_request = None #type: Optional[QNetworkRequest]
self._download_reply = None #type: Optional[QNetworkReply]
self._download_progress = 0 #type: float
self._is_downloading = False #type: bool
self._network_manager = None #type: Optional[QNetworkAccessManager]
self._download_request = None # type: Optional[QNetworkRequest]
self._download_reply = None # type: Optional[QNetworkReply]
self._download_progress = 0 # type: float
self._is_downloading = False # type: bool
self._network_manager = None # type: Optional[QNetworkAccessManager]
self._request_header = [
b"User-Agent",
str.encode(
@ -58,9 +61,10 @@ class Toolbox(QObject, Extension):
)
)
]
self._request_urls = {} # type: Dict[str, QUrl]
self._request_urls = {} # type: Dict[str, QUrl]
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
self._old_plugin_ids = [] # type: List[str]
self._old_plugin_ids = set() # type: Set[str]
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
# Data:
self._metadata = {
@ -73,7 +77,7 @@ class Toolbox(QObject, Extension):
"materials_available": [],
"materials_installed": [],
"materials_generic": []
} # type: Dict[str, List[Any]]
} # type: Dict[str, List[Any]]
# Models:
self._models = {
@ -86,39 +90,37 @@ class Toolbox(QObject, Extension):
"materials_available": AuthorsModel(self),
"materials_installed": PackagesModel(self),
"materials_generic": PackagesModel(self)
} # type: Dict[str, ListModel]
} # type: Dict[str, ListModel]
# These properties are for keeping track of the UI state:
# ----------------------------------------------------------------------
# View category defines which filter to use, and therefore effectively
# which category is currently being displayed. For example, possible
# values include "plugin" or "material", but also "installed".
self._view_category = "plugin" #type: str
self._view_category = "plugin" # type: str
# 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 = "loading" # type: str
# Active package refers to which package is currently being downloaded,
# installed, or otherwise modified.
self._active_package = None # type: Optional[Dict[str, Any]]
self._active_package = None # type: Optional[Dict[str, Any]]
self._dialog = None #type: Optional[QObject]
self._confirm_reset_dialog = None #type: Optional[QObject]
self._dialog = None # type: Optional[QObject]
self._confirm_reset_dialog = None # type: Optional[QObject]
self._resetUninstallVariables()
self._restart_required = False #type: bool
self._restart_required = False # type: bool
# variables for the license agreement dialog
self._license_dialog_plugin_name = "" #type: str
self._license_dialog_license_content = "" #type: str
self._license_dialog_plugin_file_location = "" #type: str
self._restart_dialog_message = "" #type: str
self._license_dialog_plugin_name = "" # type: str
self._license_dialog_license_content = "" # type: str
self._license_dialog_plugin_file_location = "" # type: str
self._restart_dialog_message = "" # type: str
self._application.initializationFinished.connect(self._onAppInitialized)
# Signals:
# --------------------------------------------------------------------------
# Downloading changes
@ -137,11 +139,11 @@ class Toolbox(QObject, Extension):
showLicenseDialog = pyqtSignal()
uninstallVariablesChanged = pyqtSignal()
def _resetUninstallVariables(self):
self._package_id_to_uninstall = None
def _resetUninstallVariables(self) -> None:
self._package_id_to_uninstall = None # type: Optional[str]
self._package_name_to_uninstall = ""
self._package_used_materials = []
self._package_used_qualities = []
self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]]
self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]]
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self) -> str:
@ -205,14 +207,14 @@ class Toolbox(QObject, Extension):
return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
# Get the packages version depending on Cura version settings.
def _getSDKVersion(self) -> int:
def _getSDKVersion(self) -> Union[int, str]:
if not hasattr(cura, "CuraVersion"):
return self._plugin_registry.APIVersion
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
return self._plugin_registry.APIVersion
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraSDKVersion # type: ignore
return cura.CuraVersion.CuraSDKVersion # type: ignore
@pyqtSlot()
def browsePackages(self) -> None:
@ -229,10 +231,12 @@ class Toolbox(QObject, Extension):
# Make remote requests:
self._makeRequestByType("packages")
self._makeRequestByType("authors")
self._makeRequestByType("plugins_showcase")
self._makeRequestByType("materials_showcase")
self._makeRequestByType("materials_available")
self._makeRequestByType("materials_generic")
# TODO: Uncomment in the future when the tag-filtered api calls work in the cloud server
# self._makeRequestByType("plugins_showcase")
# self._makeRequestByType("plugins_available")
# self._makeRequestByType("materials_showcase")
# self._makeRequestByType("materials_available")
# self._makeRequestByType("materials_generic")
# Gather installed packages:
self._updateInstalledModels()
@ -285,8 +289,8 @@ class Toolbox(QObject, Extension):
installed_package_ids = self._package_manager.getAllInstalledPackageIDs()
scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
self._old_plugin_ids = []
self._old_plugin_metadata = [] # type: List[Dict[str, Any]]
self._old_plugin_ids = set()
self._old_plugin_metadata = dict()
for plugin_id in old_plugin_ids:
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
@ -296,12 +300,20 @@ class Toolbox(QObject, Extension):
old_metadata = self._plugin_registry.getMetaData(plugin_id)
new_metadata = self._convertPluginMetadata(old_metadata)
self._old_plugin_ids.append(plugin_id)
self._old_plugin_metadata.append(new_metadata)
self._old_plugin_ids.add(plugin_id)
self._old_plugin_metadata[new_metadata["package_id"]] = new_metadata
all_packages = self._package_manager.getAllInstalledPackagesInfo()
if "plugin" in all_packages:
self._metadata["plugins_installed"] = all_packages["plugin"] + self._old_plugin_metadata
# For old plugins, we only want to include the old custom plugin that were installed via the old toolbox.
# The bundled plugins will be included in JSON files in the "bundled_packages" folder, so the bundled
# plugins should be excluded from the old plugins list/dict.
all_plugin_package_ids = set(package["package_id"] for package in all_packages["plugin"])
self._old_plugin_ids = set(plugin_id for plugin_id in self._old_plugin_ids
if plugin_id not in all_plugin_package_ids)
self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
self._metadata["plugins_installed"] = all_packages["plugin"] + list(self._old_plugin_metadata.values())
self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
self.metadataChanged.emit()
if "material" in all_packages:
@ -344,26 +356,26 @@ class Toolbox(QObject, Extension):
self.uninstall(package_id)
@pyqtProperty(str, notify = uninstallVariablesChanged)
def pluginToUninstall(self):
def pluginToUninstall(self) -> str:
return self._package_name_to_uninstall
@pyqtProperty(str, notify = uninstallVariablesChanged)
def uninstallUsedMaterials(self):
def uninstallUsedMaterials(self) -> str:
return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials])
@pyqtProperty(str, notify = uninstallVariablesChanged)
def uninstallUsedQualities(self):
def uninstallUsedQualities(self) -> str:
return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities])
@pyqtSlot()
def closeConfirmResetDialog(self):
def closeConfirmResetDialog(self) -> None:
if self._confirm_reset_dialog is not None:
self._confirm_reset_dialog.close()
## Uses "uninstall variables" to reset qualities and materials, then uninstall
# It's used as an action on Confirm reset on Uninstall
@pyqtSlot()
def resetMaterialsQualitiesAndUninstall(self):
def resetMaterialsQualitiesAndUninstall(self) -> None:
application = CuraApplication.getInstance()
material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager()
@ -376,9 +388,9 @@ class Toolbox(QObject, Extension):
default_quality_group = quality_manager.getDefaultQualityType(global_stack)
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
self.uninstall(self._package_id_to_uninstall)
if self._package_id_to_uninstall is not None:
self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
self.uninstall(self._package_id_to_uninstall)
self._resetUninstallVariables()
self.closeConfirmResetDialog()
@ -471,12 +483,14 @@ class Toolbox(QObject, Extension):
# --------------------------------------------------------------------------
@pyqtSlot(str, result = bool)
def canUpdate(self, package_id: str) -> bool:
if self.isOldPlugin(package_id):
return True
local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None:
return False
Logger.log("i", "Could not find package [%s] as installed in the package manager, fall back to check the old plugins",
package_id)
local_package = self.getOldPluginPackageMetadata(package_id)
if local_package is None:
Logger.log("i", "Could not find package [%s] in the old plugins", package_id)
return False
remote_package = self.getRemotePackage(package_id)
if remote_package is None:
@ -484,7 +498,16 @@ class Toolbox(QObject, Extension):
local_version = Version(local_package["package_version"])
remote_version = Version(remote_package["package_version"])
return remote_version > local_version
can_upgrade = False
if remote_version > local_version:
can_upgrade = True
# A package with the same version can be built to have different SDK versions. So, for a package with the same
# version, we also need to check if the current one has a lower SDK version. If so, this package should also
# be upgradable.
elif remote_version == local_version:
can_upgrade = local_package.get("sdk_version", 0) < remote_package.get("sdk_version", 0)
return can_upgrade
@pyqtSlot(str, result = bool)
def canDowngrade(self, package_id: str) -> bool:
@ -504,7 +527,11 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, result = bool)
def isInstalled(self, package_id: str) -> bool:
return self._package_manager.isPackageInstalled(package_id)
result = self._package_manager.isPackageInstalled(package_id)
# Also check the old plugins list if it's not found in the package manager.
if not result:
result = self.isOldPlugin(package_id)
return result
@pyqtSlot(str, result = int)
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
@ -531,12 +558,14 @@ class Toolbox(QObject, Extension):
return False
# Check for plugins that were installed with the old plugin browser
@pyqtSlot(str, result = bool)
def isOldPlugin(self, plugin_id: str) -> bool:
if plugin_id in self._old_plugin_ids:
return True
return False
def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
return self._old_plugin_metadata.get(plugin_id)
def loadingComplete(self) -> bool:
populated = 0
for list in self._metadata.items():
@ -612,6 +641,7 @@ class Toolbox(QObject, Extension):
do_not_handle = [
"materials_available",
"materials_showcase",
"materials_generic",
"plugins_available",
"plugins_showcase",
]
@ -621,7 +651,7 @@ class Toolbox(QObject, Extension):
# HACK: Do nothing because we'll handle these from the "packages" call
if type in do_not_handle:
return
continue
if reply.url() == url:
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
@ -686,7 +716,7 @@ class Toolbox(QObject, Extension):
self._temp_plugin_file.close()
self._onDownloadComplete(file_path)
def _onDownloadComplete(self, file_path: str):
def _onDownloadComplete(self, file_path: str) -> None:
Logger.log("i", "Toolbox: Download complete.")
package_info = self._package_manager.getPackageInfo(file_path)
if not package_info:
@ -745,9 +775,7 @@ class Toolbox(QObject, Extension):
def viewPage(self) -> str:
return self._view_page
# Expose Models:
# Exposed Models:
# --------------------------------------------------------------------------
@pyqtProperty(QObject, notify = metadataChanged)
def authorsModel(self) -> AuthorsModel:
@ -785,8 +813,6 @@ class Toolbox(QObject, Extension):
def materialsGenericModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["materials_generic"])
# Filter Models:
# --------------------------------------------------------------------------
@pyqtSlot(str, str, str)
@ -813,11 +839,9 @@ class Toolbox(QObject, Extension):
self._models[model_type].setFilter({})
self.filterChanged.emit()
# HACK(S):
# --------------------------------------------------------------------------
def buildMaterialsModels(self) -> None:
self._metadata["materials_showcase"] = []
self._metadata["materials_available"] = []
@ -830,18 +854,22 @@ class Toolbox(QObject, Extension):
if author["author_id"] in processed_authors:
continue
if "showcase" in item["tags"]:
self._metadata["materials_showcase"].append(author)
# Generic materials to be in the same section
if "generic" in item["tags"]:
self._metadata["materials_generic"].append(item)
else:
self._metadata["materials_available"].append(author)
if "showcase" in item["tags"]:
self._metadata["materials_showcase"].append(author)
else:
self._metadata["materials_available"].append(author)
processed_authors.append(author["author_id"])
processed_authors.append(author["author_id"])
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
self._models["materials_available"].setMetadata(self._metadata["materials_available"])
self._models["materials_generic"].setMetadata(self._metadata["materials_generic"])
def buildPluginsModels(self) -> None:
self._metadata["plugins_showcase"] = []
self._metadata["plugins_available"] = []

View file

@ -1,4 +1,5 @@
import QtQuick 2.3
import QtQuick.Dialogs 1.1
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
@ -497,8 +498,8 @@ Component
text: catalog.i18nc("@label", "Abort")
onClicked:
{
modelData.activePrintJob.setState("abort")
popup.close()
abortConfirmationDialog.visible = true;
popup.close();
}
width: parent.width
height: 39 * screenScaleFactor
@ -517,6 +518,17 @@ Component
verticalAlignment: Text.AlignVCenter
}
}
MessageDialog
{
id: abortConfirmationDialog
title: catalog.i18nc("@window:title", "Abort print")
icon: StandardIcon.Warning
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to abort %1?").arg(modelData.activePrintJob.name)
standardButtons: StandardButton.Yes | StandardButton.No
Component.onCompleted: visible = false
onYes: modelData.activePrintJob.setState("abort")
}
}
background: Item

View file

@ -1,4 +1,5 @@
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
@ -224,8 +225,8 @@ Item
text: catalog.i18nc("@label", "Move to top")
onClicked:
{
OutputDevice.sendJobToTop(printJob.key)
popup.close()
sendToTopConfirmationDialog.visible = true;
popup.close();
}
width: parent.width
enabled: OutputDevice.queuedPrintJobs[0].key != printJob.key
@ -247,14 +248,25 @@ Item
}
}
MessageDialog
{
id: sendToTopConfirmationDialog
title: catalog.i18nc("@window:title", "Move print job to top")
icon: StandardIcon.Warning
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to move %1 to the top of the queue?").arg(printJob.name)
standardButtons: StandardButton.Yes | StandardButton.No
Component.onCompleted: visible = false
onYes: OutputDevice.sendJobToTop(printJob.key)
}
Button
{
id: deleteButton
text: catalog.i18nc("@label", "Delete")
onClicked:
{
OutputDevice.deleteJobFromQueue(printJob.key)
popup.close()
deleteConfirmationDialog.visible = true;
popup.close();
}
width: parent.width
height: 39 * screenScaleFactor
@ -272,6 +284,17 @@ Item
verticalAlignment: Text.AlignVCenter
}
}
MessageDialog
{
id: deleteConfirmationDialog
title: catalog.i18nc("@window:title", "Delete print job")
icon: StandardIcon.Warning
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to delete %1?").arg(printJob.name)
standardButtons: StandardButton.Yes | StandardButton.No
Component.onCompleted: visible = false
onYes: OutputDevice.deleteJobFromQueue(printJob.key)
}
}
background: Item

View file

@ -77,6 +77,7 @@ class AutoDetectBaudJob(Job):
self.setResult(baud_rate)
Logger.log("d", "Detected baud rate {baud_rate} on serial {serial} on retry {retry} with after {time_elapsed:0.2f} seconds.".format(
serial = self._serial_port, baud_rate = baud_rate, retry = retry, time_elapsed = time() - start_timeout_time))
serial.close() # close serial port so it can be opened by the USBPrinterOutputDevice
return
serial.write(b"M105\n")

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse the files we need to upgrade and write the new files.
@ -9,8 +9,6 @@ from urllib.parse import quote_plus
from UM.Resources import Resources
from UM.VersionUpgrade import VersionUpgrade
from cura.CuraApplication import CuraApplication
_removed_settings = { #Settings that were removed in 2.5.
"start_layers_at_same_position",
"sub_div_rad_mult"
@ -152,7 +150,7 @@ class VersionUpgrade25to26(VersionUpgrade):
## Acquires the next unique extruder stack index number for the Custom FDM Printer.
def _acquireNextUniqueCustomFdmPrinterExtruderStackIdIndex(self):
extruder_stack_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
file_name_list = os.listdir(extruder_stack_dir)
file_name_list = [os.path.basename(file_name) for file_name in file_name_list]
while True:
@ -173,7 +171,7 @@ class VersionUpgrade25to26(VersionUpgrade):
def _checkCustomFdmPrinterHasExtruderStack(self, machine_id):
# go through all extruders and make sure that this custom FDM printer has extruder stacks.
extruder_stack_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
has_extruders = False
for item in os.listdir(extruder_stack_dir):
file_path = os.path.join(extruder_stack_dir, item)
@ -245,9 +243,9 @@ class VersionUpgrade25to26(VersionUpgrade):
parser.write(extruder_output)
extruder_filename = quote_plus(stack_id) + ".extruder.cfg"
extruder_stack_dir = Resources.getPath(CuraApplication.ResourceTypes.ExtruderStack)
definition_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.DefinitionChangesContainer)
user_settings_dir = Resources.getPath(CuraApplication.ResourceTypes.UserInstanceContainer)
extruder_stack_dir = os.path.join(Resources.getDataStoragePath(), "extruders")
definition_changes_dir = os.path.join(Resources.getDataStoragePath(), "definition_changes")
user_settings_dir = os.path.join(Resources.getDataStoragePath(), "user")
with open(os.path.join(definition_changes_dir, definition_changes_filename), "w", encoding = "utf-8") as f:
f.write(definition_changes_output.getvalue())

View file

@ -1,11 +1,10 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse the files we need to upgrade and write the new files.
import io #To serialise configparser output to a string.
from UM.VersionUpgrade import VersionUpgrade
from cura.CuraApplication import CuraApplication
# a dict of renamed quality profiles: <old_id> : <new_id>
_renamed_quality_profiles = {

View file

@ -269,7 +269,7 @@ class XmlMaterialProfile(InstanceContainer):
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
buildplate_dict = {} # type: Dict[str, Any]
for variant_name, variant_dict in machine_variant_map[definition_id].items():
variant_type = variant_dict["variant_node"].metadata["hardware_type"]
variant_type = variant_dict["variant_node"].getMetaDataEntry("hardware_type", str(VariantType.NOZZLE))
variant_type = VariantType(variant_type)
if variant_type == VariantType.NOZZLE:
# The hotend identifier is not the containers name, but its "name".