mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-07 05:53:59 -06:00
Merge branch 'master' into feature_amf_reader
This commit is contained in:
commit
066b90bdea
336 changed files with 57051 additions and 9240 deletions
|
@ -259,7 +259,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||
quality_name = ""
|
||||
custom_quality_name = ""
|
||||
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
|
||||
num_settings_overridden_by_quality_changes = 0 # How many settings are changed by the quality changes
|
||||
num_user_settings = 0
|
||||
quality_changes_conflict = False
|
||||
|
||||
|
@ -297,7 +297,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
custom_quality_name = parser["general"]["name"]
|
||||
values = parser["values"] if parser.has_section("values") else dict()
|
||||
num_settings_overriden_by_quality_changes += len(values)
|
||||
num_settings_overridden_by_quality_changes += len(values)
|
||||
# Check if quality changes already exists.
|
||||
quality_changes = self._container_registry.findInstanceContainers(name = custom_quality_name,
|
||||
type = "quality_changes")
|
||||
|
@ -515,7 +515,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._dialog.setNumVisibleSettings(num_visible_settings)
|
||||
self._dialog.setQualityName(quality_name)
|
||||
self._dialog.setQualityType(quality_type)
|
||||
self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
|
||||
self._dialog.setNumSettingsOverriddenByQualityChanges(num_settings_overridden_by_quality_changes)
|
||||
self._dialog.setNumUserSettings(num_user_settings)
|
||||
self._dialog.setActiveMode(active_mode)
|
||||
self._dialog.setMachineName(machine_name)
|
||||
|
@ -820,6 +820,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
|
||||
global_stack, extruder_stack)
|
||||
container_info.container = container
|
||||
container.setDirty(True)
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
for key, value in container_info.parser["values"].items():
|
||||
container_info.container.setProperty(key, "value", value)
|
||||
|
|
|
@ -41,7 +41,7 @@ class WorkspaceDialog(QObject):
|
|||
self._num_user_settings = 0
|
||||
self._active_mode = ""
|
||||
self._quality_name = ""
|
||||
self._num_settings_overriden_by_quality_changes = 0
|
||||
self._num_settings_overridden_by_quality_changes = 0
|
||||
self._quality_type = ""
|
||||
self._machine_name = ""
|
||||
self._machine_type = ""
|
||||
|
@ -151,10 +151,10 @@ class WorkspaceDialog(QObject):
|
|||
|
||||
@pyqtProperty(int, notify=numSettingsOverridenByQualityChangesChanged)
|
||||
def numSettingsOverridenByQualityChanges(self):
|
||||
return self._num_settings_overriden_by_quality_changes
|
||||
return self._num_settings_overridden_by_quality_changes
|
||||
|
||||
def setNumSettingsOverridenByQualityChanges(self, num_settings_overriden_by_quality_changes):
|
||||
self._num_settings_overriden_by_quality_changes = num_settings_overriden_by_quality_changes
|
||||
def setNumSettingsOverriddenByQualityChanges(self, num_settings_overridden_by_quality_changes):
|
||||
self._num_settings_overridden_by_quality_changes = num_settings_overridden_by_quality_changes
|
||||
self.numSettingsOverridenByQualityChangesChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=qualityNameChanged)
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Extension import Extension
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Version import Version
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
|
||||
import os.path
|
||||
import collections
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
class ChangeLog(Extension, QObject,):
|
||||
def __init__(self, parent = None):
|
||||
QObject.__init__(self, parent)
|
||||
Extension.__init__(self)
|
||||
self._changelog_window = None
|
||||
self._changelog_context = None
|
||||
version_string = Application.getInstance().getVersion()
|
||||
if version_string is not "master":
|
||||
self._current_app_version = Version(version_string)
|
||||
else:
|
||||
self._current_app_version = None
|
||||
|
||||
self._change_logs = None
|
||||
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
||||
Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
|
||||
self.setMenuName(catalog.i18nc("@item:inmenu", "Changelog"))
|
||||
self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
|
||||
|
||||
def getChangeLogs(self):
|
||||
if not self._change_logs:
|
||||
self.loadChangeLogs()
|
||||
return self._change_logs
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getChangeLogString(self):
|
||||
logs = self.getChangeLogs()
|
||||
result = ""
|
||||
for version in logs:
|
||||
result += "<h1>" + str(version) + "</h1><br>"
|
||||
result += ""
|
||||
for change in logs[version]:
|
||||
if str(change) != "":
|
||||
result += "<b>" + str(change) + "</b><br>"
|
||||
for line in logs[version][change]:
|
||||
result += str(line) + "<br>"
|
||||
result += "<br>"
|
||||
|
||||
pass
|
||||
return result
|
||||
|
||||
def loadChangeLogs(self):
|
||||
self._change_logs = collections.OrderedDict()
|
||||
with open(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.txt"), "r", encoding = "utf-8") as f:
|
||||
open_version = None
|
||||
open_header = "" # Initialise to an empty header in case there is no "*" in the first line of the changelog
|
||||
for line in f:
|
||||
line = line.replace("\n","")
|
||||
if "[" in line and "]" in line:
|
||||
line = line.replace("[","")
|
||||
line = line.replace("]","")
|
||||
open_version = Version(line)
|
||||
open_header = ""
|
||||
self._change_logs[open_version] = collections.OrderedDict()
|
||||
elif line.startswith("*"):
|
||||
open_header = line.replace("*","")
|
||||
self._change_logs[open_version][open_header] = []
|
||||
elif line != "":
|
||||
if open_header not in self._change_logs[open_version]:
|
||||
self._change_logs[open_version][open_header] = []
|
||||
self._change_logs[open_version][open_header].append(line)
|
||||
|
||||
def _onEngineCreated(self):
|
||||
if not self._current_app_version:
|
||||
return #We're on dev branch.
|
||||
|
||||
if Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown") == "master":
|
||||
latest_version_shown = Version("0.0.0")
|
||||
else:
|
||||
latest_version_shown = Version(Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown"))
|
||||
|
||||
Application.getInstance().getPreferences().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
|
||||
|
||||
# Do not show the changelog when there is no global container stack
|
||||
# This implies we are running Cura for the first time.
|
||||
if not Application.getInstance().getGlobalContainerStack():
|
||||
return
|
||||
|
||||
if self._current_app_version > latest_version_shown:
|
||||
self.showChangelog()
|
||||
|
||||
def showChangelog(self):
|
||||
if not self._changelog_window:
|
||||
self.createChangelogWindow()
|
||||
|
||||
self._changelog_window.show()
|
||||
|
||||
def hideChangelog(self):
|
||||
if self._changelog_window:
|
||||
self._changelog_window.hide()
|
||||
|
||||
def createChangelogWindow(self):
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")
|
||||
self._changelog_window = Application.getInstance().createQmlComponent(path, {"manager": self})
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) 2015 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Controls 1.3
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: base
|
||||
minimumWidth: (UM.Theme.getSize("modal_window_minimum").width * 0.75) | 0
|
||||
minimumHeight: (UM.Theme.getSize("modal_window_minimum").height * 0.75) | 0
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
title: catalog.i18nc("@label", "Changelog")
|
||||
|
||||
TextArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
text: manager.getChangeLogString()
|
||||
readOnly: true;
|
||||
textFormat: TextEdit.RichText
|
||||
}
|
||||
|
||||
rightButtons: [
|
||||
Button
|
||||
{
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
text: catalog.i18nc("@action:button", "Close")
|
||||
onClicked: base.hide()
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,11 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import ChangeLog
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return {"extension": ChangeLog.ChangeLog()}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"name": "Changelog",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Shows changes since latest checked version.",
|
||||
"api": "6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -45,7 +45,7 @@ class DriveApiService:
|
|||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
except requests.exceptions.ConnectionError:
|
||||
Logger.log("w", "Unable to connect with the server.")
|
||||
Logger.logException("w", "Unable to connect with the server.")
|
||||
return []
|
||||
|
||||
# HTTP status 300s mean redirection. 400s and 500s are errors.
|
||||
|
@ -98,7 +98,12 @@ class DriveApiService:
|
|||
# If there is no download URL, we can't restore the backup.
|
||||
return self._emitRestoreError()
|
||||
|
||||
download_package = requests.get(download_url, stream = True)
|
||||
try:
|
||||
download_package = requests.get(download_url, stream = True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
Logger.logException("e", "Unable to connect with the server")
|
||||
return self._emitRestoreError()
|
||||
|
||||
if download_package.status_code >= 300:
|
||||
# Something went wrong when attempting to download the backup.
|
||||
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
|
||||
|
@ -142,9 +147,14 @@ class DriveApiService:
|
|||
Logger.log("w", "Could not get access token.")
|
||||
return False
|
||||
|
||||
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
try:
|
||||
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
except requests.exceptions.ConnectionError:
|
||||
Logger.logException("e", "Unable to connect with the server")
|
||||
return False
|
||||
|
||||
if delete_backup.status_code >= 300:
|
||||
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
|
||||
return False
|
||||
|
@ -159,15 +169,19 @@ class DriveApiService:
|
|||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return None
|
||||
|
||||
backup_upload_request = requests.put(self.BACKUP_URL, json = {
|
||||
"data": {
|
||||
"backup_size": backup_size,
|
||||
"metadata": backup_metadata
|
||||
}
|
||||
}, headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
try:
|
||||
backup_upload_request = requests.put(
|
||||
self.BACKUP_URL,
|
||||
json = {"data": {"backup_size": backup_size,
|
||||
"metadata": backup_metadata
|
||||
}
|
||||
},
|
||||
headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
except requests.exceptions.ConnectionError:
|
||||
Logger.logException("e", "Unable to connect with the server")
|
||||
return None
|
||||
|
||||
# Any status code of 300 or above indicates an error.
|
||||
if backup_upload_request.status_code >= 300:
|
||||
|
|
|
@ -10,20 +10,17 @@ from time import time
|
|||
from typing import Any, cast, Dict, List, Optional, Set, TYPE_CHECKING
|
||||
|
||||
from UM.Backend.Backend import Backend, BackendState
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Signal import Signal
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from UM.Platform import Platform
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||
from UM.Settings.SettingInstance import SettingInstance #For typing.
|
||||
from UM.Tool import Tool #For typing.
|
||||
from UM.Mesh.MeshData import MeshData #For typing.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
|
|
@ -24,7 +24,7 @@ from cura import LayerPolygon
|
|||
|
||||
import numpy
|
||||
from time import time
|
||||
from cura.Settings.ExtrudersModel import ExtrudersModel
|
||||
from cura.Machines.Models.ExtrudersModel import ExtrudersModel
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
|
|
|
@ -196,10 +196,7 @@ class StartSliceJob(Job):
|
|||
has_printing_mesh = False
|
||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
||||
per_object_stack = node.callDecoration("getStack")
|
||||
is_non_printing_mesh = False
|
||||
if per_object_stack:
|
||||
is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
|
||||
is_non_printing_mesh = bool(node.callDecoration("isNonPrintingMesh"))
|
||||
|
||||
# Find a reason not to add the node
|
||||
if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import math
|
||||
import re
|
||||
from typing import Dict, List, NamedTuple, Optional, Union
|
||||
|
||||
import numpy
|
||||
|
||||
from UM.Backend import Backend
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Message import Message
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.LayerDataBuilder import LayerDataBuilder
|
||||
from cura.LayerDataDecorator import LayerDataDecorator
|
||||
from cura.LayerPolygon import LayerPolygon
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
import numpy
|
||||
import math
|
||||
import re
|
||||
from typing import Dict, List, NamedTuple, Optional, Union
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
|
||||
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])])
|
||||
|
||||
|
||||
## This parser is intended to interpret the common firmware codes among all the
|
||||
# different flavors
|
||||
class FlavorParser:
|
||||
|
@ -33,7 +35,7 @@ class FlavorParser:
|
|||
def __init__(self) -> None:
|
||||
CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
||||
self._cancelled = False
|
||||
self._message = None
|
||||
self._message = None # type: Optional[Message]
|
||||
self._layer_number = 0
|
||||
self._extruder_number = 0
|
||||
self._clearValues()
|
||||
|
@ -425,7 +427,8 @@ class FlavorParser:
|
|||
|
||||
if line.startswith("M"):
|
||||
M = self._getInt(line, "M")
|
||||
self.processMCode(M, line, current_position, current_path)
|
||||
if M is not None:
|
||||
self.processMCode(M, line, current_position, current_path)
|
||||
|
||||
# "Flush" leftovers. Last layer paths are still stored
|
||||
if len(current_path) > 1:
|
||||
|
@ -463,7 +466,7 @@ class FlavorParser:
|
|||
Logger.log("w", "File doesn't contain any valid layers")
|
||||
|
||||
settings = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not settings.getProperty("machine_center_is_zero", "value"):
|
||||
if settings is not None and not settings.getProperty("machine_center_is_zero", "value"):
|
||||
machine_width = settings.getProperty("machine_width", "value")
|
||||
machine_depth = settings.getProperty("machine_depth", "value")
|
||||
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
|
||||
|
|
|
@ -12,9 +12,6 @@ catalog = i18nCatalog("cura")
|
|||
from . import MarlinFlavorParser, RepRapFlavorParser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Class for loading and parsing G-code files
|
||||
class GCodeReader(MeshReader):
|
||||
_flavor_default = "Marlin"
|
||||
|
|
|
@ -123,7 +123,7 @@ UM.Dialog
|
|||
UM.TooltipArea {
|
||||
Layout.fillWidth:true
|
||||
height: childrenRect.height
|
||||
text: catalog.i18nc("@info:tooltip","By default, white pixels represent high points on the mesh and black pixels represent low points on the mesh. Change this option to reverse the behavior such that black pixels represent high points on the mesh and white pixels represent low points on the mesh.")
|
||||
text: catalog.i18nc("@info:tooltip","For lithophanes dark pixels should correspond to thicker locations in order to block more light coming through. For height maps lighter pixels signify higher terrain, so lighter pixels should correspond to thicker locations in the generated 3D model.")
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
|
@ -134,9 +134,9 @@ UM.Dialog
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
ComboBox {
|
||||
id: image_color_invert
|
||||
objectName: "Image_Color_Invert"
|
||||
model: [ catalog.i18nc("@item:inlistbox","Lighter is higher"), catalog.i18nc("@item:inlistbox","Darker is higher") ]
|
||||
id: lighter_is_higher
|
||||
objectName: "Lighter_Is_Higher"
|
||||
model: [ catalog.i18nc("@item:inlistbox","Darker is higher"), catalog.i18nc("@item:inlistbox","Lighter is higher") ]
|
||||
width: 180 * screenScaleFactor
|
||||
onCurrentIndexChanged: { manager.onImageColorInvertChanged(currentIndex) }
|
||||
}
|
||||
|
|
|
@ -46,9 +46,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.image_color_invert)
|
||||
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher)
|
||||
|
||||
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
|
||||
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher):
|
||||
scene_node = SceneNode()
|
||||
|
||||
mesh = MeshBuilder()
|
||||
|
@ -104,7 +104,7 @@ class ImageReader(MeshReader):
|
|||
|
||||
Job.yieldThread()
|
||||
|
||||
if image_color_invert:
|
||||
if not lighter_is_higher:
|
||||
height_data = 1 - height_data
|
||||
|
||||
for _ in range(0, blur_iterations):
|
||||
|
|
|
@ -30,10 +30,10 @@ class ImageReaderUI(QObject):
|
|||
self._width = self.default_width
|
||||
self._depth = self.default_depth
|
||||
|
||||
self.base_height = 1
|
||||
self.peak_height = 10
|
||||
self.base_height = 0.4
|
||||
self.peak_height = 2.5
|
||||
self.smoothing = 1
|
||||
self.image_color_invert = False;
|
||||
self.lighter_is_higher = False;
|
||||
|
||||
self._ui_lock = threading.Lock()
|
||||
self._cancelled = False
|
||||
|
@ -143,4 +143,4 @@ class ImageReaderUI(QObject):
|
|||
|
||||
@pyqtSlot(int)
|
||||
def onImageColorInvertChanged(self, value):
|
||||
self.image_color_invert = (value == 1)
|
||||
self.lighter_is_higher = (value == 1)
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty
|
||||
|
||||
import UM.i18n
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Application import Application
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
|
||||
from cura.MachineAction import MachineAction
|
||||
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||
from cura.Settings.cura_empty_instance_containers import isEmptyContainer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtCore import QObject
|
||||
|
||||
catalog = UM.i18n.i18nCatalog("cura")
|
||||
|
||||
|
@ -18,139 +23,102 @@ catalog = UM.i18n.i18nCatalog("cura")
|
|||
## This action allows for certain settings that are "machine only") to be modified.
|
||||
# It automatically detects machine definitions that it knows how to change and attaches itself to those.
|
||||
class MachineSettingsAction(MachineAction):
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
|
||||
self._qml_url = "MachineSettingsAction.qml"
|
||||
|
||||
self._application = Application.getInstance()
|
||||
|
||||
self._global_container_stack = None
|
||||
from cura.CuraApplication import CuraApplication
|
||||
self._application = CuraApplication.getInstance()
|
||||
|
||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||
self._container_index = _ContainerIndexes.DefinitionChanges
|
||||
self._store_container_index = _ContainerIndexes.DefinitionChanges
|
||||
|
||||
self._container_registry = ContainerRegistry.getInstance()
|
||||
self._container_registry.containerAdded.connect(self._onContainerAdded)
|
||||
self._container_registry.containerRemoved.connect(self._onContainerRemoved)
|
||||
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
|
||||
# The machine settings dialog blocks auto-slicing when it's shown, and re-enables it when it's finished.
|
||||
self._backend = self._application.getBackend()
|
||||
self.onFinished.connect(self._onFinished)
|
||||
|
||||
self._empty_definition_container_id_list = []
|
||||
|
||||
def _isEmptyDefinitionChanges(self, container_id: str):
|
||||
if not self._empty_definition_container_id_list:
|
||||
self._empty_definition_container_id_list = [self._application.empty_container.getId(),
|
||||
self._application.empty_definition_changes_container.getId()]
|
||||
return container_id in self._empty_definition_container_id_list
|
||||
# Which container index in a stack to store machine setting changes.
|
||||
@pyqtProperty(int, constant = True)
|
||||
def storeContainerIndex(self) -> int:
|
||||
return self._store_container_index
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
# Add this action as a supported action to all machine definitions
|
||||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
||||
self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||
|
||||
def _onContainerRemoved(self, container):
|
||||
# Remove definition_changes containers when a stack is removed
|
||||
if container.getMetaDataEntry("type") in ["machine", "extruder_train"]:
|
||||
definition_changes_id = container.definitionChanges.getId()
|
||||
if self._isEmptyDefinitionChanges(definition_changes_id):
|
||||
return
|
||||
|
||||
def _reset(self):
|
||||
if not self._global_container_stack:
|
||||
global_stack = self._application.getMachineManager().activeMachine
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
# Make sure there is a definition_changes container to store the machine settings
|
||||
definition_changes_id = self._global_container_stack.definitionChanges.getId()
|
||||
if self._isEmptyDefinitionChanges(definition_changes_id):
|
||||
CuraStackBuilder.createDefinitionChangesContainer(self._global_container_stack,
|
||||
self._global_container_stack.getName() + "_settings")
|
||||
|
||||
# Notify the UI in which container to store the machine settings data
|
||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||
|
||||
container_index = _ContainerIndexes.DefinitionChanges
|
||||
if container_index != self._container_index:
|
||||
self._container_index = container_index
|
||||
self.containerIndexChanged.emit()
|
||||
definition_changes_id = global_stack.definitionChanges.getId()
|
||||
if isEmptyContainer(definition_changes_id):
|
||||
CuraStackBuilder.createDefinitionChangesContainer(global_stack,
|
||||
global_stack.getName() + "_settings")
|
||||
|
||||
# Disable auto-slicing while the MachineAction is showing
|
||||
if self._backend: # This sometimes triggers before backend is loaded.
|
||||
self._backend.disableTimer()
|
||||
|
||||
@pyqtSlot()
|
||||
def onFinishAction(self):
|
||||
# Restore autoslicing when the machineaction is dismissed
|
||||
def _onFinished(self):
|
||||
# Restore auto-slicing when the machine action is dismissed
|
||||
if self._backend and self._backend.determineAutoSlicing():
|
||||
self._backend.enableTimer()
|
||||
self._backend.tickle()
|
||||
|
||||
containerIndexChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(int, notify = containerIndexChanged)
|
||||
def containerIndex(self):
|
||||
return self._container_index
|
||||
|
||||
def _onGlobalContainerChanged(self):
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
# This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal
|
||||
self.globalContainerChanged.emit()
|
||||
|
||||
globalContainerChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(int, notify = globalContainerChanged)
|
||||
def definedExtruderCount(self):
|
||||
if not self._global_container_stack:
|
||||
return 0
|
||||
|
||||
return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setMachineExtruderCount(self, extruder_count):
|
||||
def setMachineExtruderCount(self, extruder_count: int) -> None:
|
||||
# Note: this method was in this class before, but since it's quite generic and other plugins also need it
|
||||
# it was moved to the machine manager instead. Now this method just calls the machine manager.
|
||||
self._application.getMachineManager().setActiveMachineExtruderCount(extruder_count)
|
||||
|
||||
@pyqtSlot()
|
||||
def forceUpdate(self):
|
||||
def forceUpdate(self) -> None:
|
||||
# Force rebuilding the build volume by reloading the global container stack.
|
||||
# This is a bit of a hack, but it seems quick enough.
|
||||
self._application.globalContainerStackChanged.emit()
|
||||
self._application.getMachineManager().globalContainerChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def updateHasMaterialsMetadata(self):
|
||||
def updateHasMaterialsMetadata(self) -> None:
|
||||
global_stack = self._application.getMachineManager().activeMachine
|
||||
|
||||
# Updates the has_materials metadata flag after switching gcode flavor
|
||||
if not self._global_container_stack:
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
definition = self._global_container_stack.getBottom()
|
||||
definition = global_stack.getDefinition()
|
||||
if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False):
|
||||
# In other words: only continue for the UM2 (extended), but not for the UM2+
|
||||
return
|
||||
|
||||
machine_manager = self._application.getMachineManager()
|
||||
material_manager = self._application.getMaterialManager()
|
||||
extruder_positions = list(self._global_container_stack.extruders.keys())
|
||||
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
||||
extruder_positions = list(global_stack.extruders.keys())
|
||||
has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
||||
|
||||
material_node = None
|
||||
if has_materials:
|
||||
self._global_container_stack.setMetaDataEntry("has_materials", True)
|
||||
global_stack.setMetaDataEntry("has_materials", True)
|
||||
else:
|
||||
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
||||
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
||||
if "has_materials" in self._global_container_stack.getMetaData():
|
||||
self._global_container_stack.removeMetaDataEntry("has_materials")
|
||||
if "has_materials" in global_stack.getMetaData():
|
||||
global_stack.removeMetaDataEntry("has_materials")
|
||||
|
||||
# set materials
|
||||
for position in extruder_positions:
|
||||
if has_materials:
|
||||
material_node = material_manager.getDefaultMaterial(self._global_container_stack, position, None)
|
||||
material_node = material_manager.getDefaultMaterial(global_stack, position, None)
|
||||
machine_manager.setMaterial(position, material_node)
|
||||
|
||||
self._application.globalContainerStackChanged.emit()
|
||||
|
||||
@pyqtSlot(int)
|
||||
def updateMaterialForDiameter(self, extruder_position: int):
|
||||
def updateMaterialForDiameter(self, extruder_position: int) -> None:
|
||||
# Updates the material container to a material that matches the material diameter set for the printer
|
||||
self._application.getMachineManager().updateMaterialWithVariant(str(extruder_position))
|
||||
|
|
File diff suppressed because it is too large
Load diff
180
plugins/MachineSettingsAction/MachineSettingsExtruderTab.qml
Normal file
180
plugins/MachineSettingsAction/MachineSettingsExtruderTab.qml
Normal file
|
@ -0,0 +1,180 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
|
||||
//
|
||||
// This component contains the content for the "Welcome" page of the welcome on-boarding process.
|
||||
//
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
property int labelWidth: 210 * screenScaleFactor
|
||||
property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0
|
||||
property var labelFont: UM.Theme.getFont("medium")
|
||||
|
||||
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
|
||||
property int columnSpacing: 3 * screenScaleFactor
|
||||
property int propertyStoreIndex: manager.storeContainerIndex // definition_changes
|
||||
|
||||
property string extruderStackId: ""
|
||||
property int extruderPosition: 0
|
||||
property var forceUpdateFunction: manager.forceUpdate
|
||||
|
||||
function updateMaterialDiameter()
|
||||
{
|
||||
manager.updateMaterialForDiameter(extruderPosition)
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: upperBlock
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
height: childrenRect.height
|
||||
|
||||
// =======================================
|
||||
// Left-side column "Nozzle Settings"
|
||||
// =======================================
|
||||
Column
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: parent.width * 2 / 3
|
||||
|
||||
spacing: base.columnSpacing
|
||||
|
||||
Label // Title Label
|
||||
{
|
||||
text: catalog.i18nc("@title:label", "Nozzle Settings")
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Nozzle size"
|
||||
{
|
||||
id: extruderNozzleSizeField
|
||||
visible: !Cura.MachineManager.hasVariants
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "machine_nozzle_size"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Nozzle size")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Compatible material diameter"
|
||||
{
|
||||
id: extruderCompatibleMaterialDiameterField
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "material_diameter"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Compatible material diameter")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
// Other modules won't automatically respond after the user changes the value, so we need to force it.
|
||||
afterOnEditingFinishedFunction: updateMaterialDiameter
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Nozzle offset X"
|
||||
{
|
||||
id: extruderNozzleOffsetXField
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "machine_nozzle_offset_x"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Nozzle offset X")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Nozzle offset Y"
|
||||
{
|
||||
id: extruderNozzleOffsetYField
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "machine_nozzle_offset_y"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Nozzle offset Y")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Cooling Fan Number"
|
||||
{
|
||||
id: extruderNozzleCoolingFanNumberField
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "machine_extruder_cooling_fan_number"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Cooling Fan Number")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: ""
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item // Extruder Start and End G-code
|
||||
{
|
||||
id: lowerBlock
|
||||
anchors.top: upperBlock.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Cura.GcodeTextArea // "Extruder Start G-code"
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
width: base.columnWidth - UM.Theme.getSize("default_margin").width
|
||||
|
||||
labelText: catalog.i18nc("@title:label", "Extruder Start G-code")
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "machine_extruder_start_code"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
}
|
||||
|
||||
Cura.GcodeTextArea // "Extruder End G-code"
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.right: parent.right
|
||||
width: base.columnWidth - UM.Theme.getSize("default_margin").width
|
||||
|
||||
labelText: catalog.i18nc("@title:label", "Extruder End G-code")
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "machine_extruder_end_code"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
}
|
||||
}
|
||||
}
|
341
plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml
Normal file
341
plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml
Normal file
|
@ -0,0 +1,341 @@
|
|||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
|
||||
//
|
||||
// This the content in the "Printer" tab in the Machine Settings dialog.
|
||||
//
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
property int labelWidth: 120 * screenScaleFactor
|
||||
property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0
|
||||
property var labelFont: UM.Theme.getFont("default")
|
||||
|
||||
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
|
||||
property int columnSpacing: 3 * screenScaleFactor
|
||||
property int propertyStoreIndex: manager.storeContainerIndex // definition_changes
|
||||
|
||||
property string machineStackId: Cura.MachineManager.activeMachineId
|
||||
|
||||
property var forceUpdateFunction: manager.forceUpdate
|
||||
|
||||
Item
|
||||
{
|
||||
id: upperBlock
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
height: childrenRect.height
|
||||
|
||||
// =======================================
|
||||
// Left-side column for "Printer Settings"
|
||||
// =======================================
|
||||
Column
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: base.columnWidth
|
||||
|
||||
spacing: base.columnSpacing
|
||||
|
||||
Label // Title Label
|
||||
{
|
||||
text: catalog.i18nc("@title:label", "Printer Settings")
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "X (Width)"
|
||||
{
|
||||
id: machineXWidthField
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_width"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "X (Width)")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Y (Depth)"
|
||||
{
|
||||
id: machineYDepthField
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_depth"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Y (Depth)")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Z (Height)"
|
||||
{
|
||||
id: machineZHeightField
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_height"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Z (Height)")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.ComboBoxWithOptions // "Build plate shape"
|
||||
{
|
||||
id: buildPlateShapeComboBox
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_shape"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Build plate shape")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.SimpleCheckBox // "Origin at center"
|
||||
{
|
||||
id: originAtCenterCheckBox
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_center_is_zero"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Origin at center")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.SimpleCheckBox // "Heated bed"
|
||||
{
|
||||
id: heatedBedCheckBox
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_heated_bed"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Heated bed")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.ComboBoxWithOptions // "G-code flavor"
|
||||
{
|
||||
id: gcodeFlavorComboBox
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_gcode_flavor"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "G-code flavor")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
// FIXME(Lipu): better document this.
|
||||
// This has something to do with UM2 and UM2+ regarding "has_material" and the gcode flavor settings.
|
||||
// I don't remember exactly what.
|
||||
afterOnEditingFinishedFunction: manager.updateHasMaterialsMetadata
|
||||
}
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Right-side column for "Printhead Settings"
|
||||
// =======================================
|
||||
Column
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: base.columnWidth
|
||||
|
||||
spacing: base.columnSpacing
|
||||
|
||||
Label // Title Label
|
||||
{
|
||||
text: catalog.i18nc("@title:label", "Printhead Settings")
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Cura.PrintHeadMinMaxTextField // "X min"
|
||||
{
|
||||
id: machineXMinField
|
||||
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
|
||||
labelText: catalog.i18nc("@label", "X min")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
|
||||
axisName: "x"
|
||||
axisMinOrMax: "min"
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.PrintHeadMinMaxTextField // "Y min"
|
||||
{
|
||||
id: machineYMinField
|
||||
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
|
||||
labelText: catalog.i18nc("@label", "Y min")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
|
||||
axisName: "y"
|
||||
axisMinOrMax: "min"
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.PrintHeadMinMaxTextField // "X max"
|
||||
{
|
||||
id: machineXMaxField
|
||||
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
|
||||
labelText: catalog.i18nc("@label", "X max")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
|
||||
axisName: "x"
|
||||
axisMinOrMax: "max"
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.PrintHeadMinMaxTextField // "Y max"
|
||||
{
|
||||
id: machineYMaxField
|
||||
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_head_with_fans_polygon"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
|
||||
labelText: catalog.i18nc("@label", "Y max")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
|
||||
axisName: "y"
|
||||
axisMinOrMax: "max"
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "Gantry Height"
|
||||
{
|
||||
id: machineGantryHeightField
|
||||
containerStackId: machineStackId
|
||||
settingKey: "gantry_height"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Gantry Height")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
Cura.ComboBoxWithOptions // "Number of Extruders"
|
||||
{
|
||||
id: numberOfExtrudersComboBox
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_extruder_count"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
labelText: catalog.i18nc("@label", "Number of Extruders")
|
||||
labelFont: base.labelFont
|
||||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
// FIXME(Lipu): better document this.
|
||||
// This has something to do with UM2 and UM2+ regarding "has_material" and the gcode flavor settings.
|
||||
// I don't remember exactly what.
|
||||
afterOnEditingFinishedFunction: manager.updateHasMaterialsMetadata
|
||||
setValueFunction: manager.setMachineExtruderCount
|
||||
|
||||
optionModel: ListModel
|
||||
{
|
||||
id: extruderCountModel
|
||||
Component.onCompleted:
|
||||
{
|
||||
extruderCountModel.clear()
|
||||
for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++)
|
||||
{
|
||||
// Use String as value. JavaScript only has Number. PropertyProvider.setPropertyValue()
|
||||
// takes a QVariant as value, and Number gets translated into a float. This will cause problem
|
||||
// for integer settings such as "Number of Extruders".
|
||||
extruderCountModel.append({ text: String(i), value: String(i) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item // Start and End G-code
|
||||
{
|
||||
id: lowerBlock
|
||||
anchors.top: upperBlock.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Cura.GcodeTextArea // "Start G-code"
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
width: base.columnWidth - UM.Theme.getSize("default_margin").width
|
||||
|
||||
labelText: catalog.i18nc("@title:label", "Start G-code")
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_start_gcode"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
}
|
||||
|
||||
Cura.GcodeTextArea // "End G-code"
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.right: parent.right
|
||||
width: base.columnWidth - UM.Theme.getSize("default_margin").width
|
||||
|
||||
labelText: catalog.i18nc("@title:label", "End G-code")
|
||||
containerStackId: machineStackId
|
||||
settingKey: "machine_end_gcode"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import os.path
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from cura.Stages.CuraStage import CuraStage
|
||||
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
loaded_script = importlib.util.module_from_spec(spec)
|
||||
if spec.loader is None:
|
||||
continue
|
||||
spec.loader.exec_module(loaded_script)
|
||||
spec.loader.exec_module(loaded_script) # type: ignore
|
||||
sys.modules[script_name] = loaded_script #TODO: This could be a security risk. Overwrite any module with a user-provided name?
|
||||
|
||||
loaded_class = getattr(loaded_script, script_name)
|
||||
|
|
|
@ -97,7 +97,7 @@ class FilamentChange(Script):
|
|||
if layer_num <= len(data):
|
||||
index, layer_data = self._searchLayerData(data, layer_num - 1)
|
||||
if layer_data is None:
|
||||
Logger.log("e", "Could not found the layer")
|
||||
Logger.log("e", "Could not find the layer {layer_num}".format(layer_num = layer_num))
|
||||
continue
|
||||
lines = layer_data.split("\n")
|
||||
lines.insert(2, color_change)
|
||||
|
|
|
@ -20,11 +20,19 @@ Item
|
|||
name: "cura"
|
||||
}
|
||||
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: UM.Theme.getSize("wide_margin").width
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
|
||||
// Item to ensure that all of the buttons are nicely centered.
|
||||
Item
|
||||
{
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: openFileButton.width + itemRow.width + UM.Theme.getSize("default_margin").width
|
||||
width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
|
||||
height: parent.height
|
||||
|
||||
RowLayout
|
||||
|
@ -32,9 +40,9 @@ Item
|
|||
id: itemRow
|
||||
|
||||
anchors.left: openFileButton.right
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
width: Math.round(0.9 * prepareMenu.width)
|
||||
height: parent.height
|
||||
spacing: 0
|
||||
|
||||
|
|
|
@ -20,15 +20,21 @@ Item
|
|||
name: "cura"
|
||||
}
|
||||
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: UM.Theme.getSize("wide_margin").width
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: stageMenuRow
|
||||
anchors.centerIn: parent
|
||||
height: parent.height
|
||||
width: childrenRect.width
|
||||
|
||||
// We want this row to have a preferred with equals to the 85% of the parent
|
||||
property int preferredWidth: Math.round(0.85 * previewMenu.width)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
|
||||
height: parent.height
|
||||
|
||||
Cura.ViewsSelector
|
||||
{
|
||||
|
@ -49,12 +55,12 @@ Item
|
|||
color: UM.Theme.getColor("lining")
|
||||
}
|
||||
|
||||
// This component will grow freely up to complete the preferredWidth of the row.
|
||||
// This component will grow freely up to complete the width of the row.
|
||||
Loader
|
||||
{
|
||||
id: viewPanel
|
||||
height: parent.height
|
||||
width: source != "" ? (stageMenuRow.preferredWidth - viewsSelector.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width) : 0
|
||||
width: source != "" ? (previewMenu.width - viewsSelector.width - printSetupSelectorItem.width - 2 * (UM.Theme.getSize("wide_margin").width + UM.Theme.getSize("default_lining").width)) : 0
|
||||
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ Cura.ExpandableComponent
|
|||
{
|
||||
id: base
|
||||
|
||||
dragPreferencesNamePrefix: "view/colorscheme"
|
||||
|
||||
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
|
||||
|
||||
Connections
|
||||
|
@ -177,7 +179,6 @@ Cura.ExpandableComponent
|
|||
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
width: parent.width
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
enabled: index < 4
|
||||
|
||||
onClicked:
|
||||
{
|
||||
|
|
|
@ -1,150 +1,154 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
|
||||
UM.Dialog
|
||||
Window
|
||||
{
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
id: baseDialog
|
||||
title: catalog.i18nc("@title:window", "More information on anonymous data collection")
|
||||
visible: false
|
||||
|
||||
modality: Qt.ApplicationModal
|
||||
|
||||
minimumWidth: 500 * screenScaleFactor
|
||||
minimumHeight: 400 * screenScaleFactor
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
|
||||
property bool allowSendData: true // for saving the user's choice
|
||||
color: UM.Theme.getColor("main_background")
|
||||
|
||||
onAccepted: manager.setSendSliceInfo(allowSendData)
|
||||
property bool allowSendData: true // for saving the user's choice
|
||||
|
||||
onVisibilityChanged:
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
baseDialog.allowSendData = UM.Preferences.getValue("info/send_slice_info");
|
||||
baseDialog.allowSendData = UM.Preferences.getValue("info/send_slice_info")
|
||||
if (baseDialog.allowSendData)
|
||||
{
|
||||
allowSendButton.checked = true;
|
||||
allowSendButton.checked = true
|
||||
}
|
||||
else
|
||||
{
|
||||
dontSendButton.checked = true;
|
||||
dontSendButton.checked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content area
|
||||
Item
|
||||
{
|
||||
id: textRow
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: radioButtonsRow.top
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
anchors.fill: parent
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Label
|
||||
Item // Text part
|
||||
{
|
||||
id: headerText
|
||||
id: textRow
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: catalog.i18nc("@text:window", "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent.")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
TextArea
|
||||
{
|
||||
id: exampleData
|
||||
anchors
|
||||
{
|
||||
top: headerText.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
bottom: parent.bottom
|
||||
bottom: radioButtonsRow.top
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: manager.getExampleData()
|
||||
readOnly: true
|
||||
textFormat: TextEdit.PlainText
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: radioButtonsRow
|
||||
width: parent.width
|
||||
anchors.bottom: buttonRow.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
ExclusiveGroup { id: group }
|
||||
|
||||
RadioButton
|
||||
{
|
||||
id: dontSendButton
|
||||
text: catalog.i18nc("@text:window", "I don't want to send this data")
|
||||
exclusiveGroup: group
|
||||
onClicked:
|
||||
Label
|
||||
{
|
||||
baseDialog.allowSendData = !checked;
|
||||
id: headerText
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
text: catalog.i18nc("@text:window", "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent.")
|
||||
wrapMode: Text.WordWrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
RadioButton
|
||||
{
|
||||
id: allowSendButton
|
||||
text: catalog.i18nc("@text:window", "Allow sending this data to Ultimaker and help us improve Cura")
|
||||
exclusiveGroup: group
|
||||
onClicked:
|
||||
|
||||
Cura.ScrollableTextArea
|
||||
{
|
||||
baseDialog.allowSendData = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
anchors
|
||||
{
|
||||
top: headerText.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
bottom: parent.bottom
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: buttonRow
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
Button
|
||||
{
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@action:button", "OK")
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.accepted()
|
||||
baseDialog.hide()
|
||||
textArea.text: manager.getExampleData()
|
||||
textArea.readOnly: true
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
Column // Radio buttons for agree and disagree
|
||||
{
|
||||
id: radioButtonsRow
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@action:button", "Cancel")
|
||||
onClicked:
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: buttonRow.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Cura.RadioButton
|
||||
{
|
||||
baseDialog.rejected()
|
||||
baseDialog.hide()
|
||||
id: dontSendButton
|
||||
text: catalog.i18nc("@text:window", "I don't want to send this data")
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.allowSendData = !checked
|
||||
}
|
||||
}
|
||||
Cura.RadioButton
|
||||
{
|
||||
id: allowSendButton
|
||||
text: catalog.i18nc("@text:window", "Allow sending this data to Ultimaker and help us improve Cura")
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.allowSendData = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item // Bottom buttons
|
||||
{
|
||||
id: buttonRow
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
height: childrenRect.height
|
||||
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@action:button", "OK")
|
||||
onClicked:
|
||||
{
|
||||
manager.setSendSliceInfo(allowSendData)
|
||||
baseDialog.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@action:button", "Cancel")
|
||||
onClicked:
|
||||
{
|
||||
baseDialog.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,20 +48,6 @@ class SliceInfo(QObject, Extension):
|
|||
def _onAppInitialized(self):
|
||||
# DO NOT read any preferences values in the constructor because at the time plugins are created, no version
|
||||
# upgrade has been performed yet because version upgrades are plugins too!
|
||||
if not self._application.getPreferences().getValue("info/asked_send_slice_info"):
|
||||
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."),
|
||||
lifetime = 0,
|
||||
dismissable = False,
|
||||
title = catalog.i18nc("@info:title", "Collecting Data"))
|
||||
|
||||
self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None,
|
||||
description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK)
|
||||
|
||||
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
|
||||
description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
|
||||
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
|
||||
self.send_slice_info_message.show()
|
||||
|
||||
if self._more_info_dialog is None:
|
||||
self._more_info_dialog = self._createDialog("MoreInfoWindow.qml")
|
||||
|
||||
|
@ -76,7 +62,7 @@ class SliceInfo(QObject, Extension):
|
|||
def showMoreInfoDialog(self):
|
||||
if self._more_info_dialog is None:
|
||||
self._more_info_dialog = self._createDialog("MoreInfoWindow.qml")
|
||||
self._more_info_dialog.open()
|
||||
self._more_info_dialog.show()
|
||||
|
||||
def _createDialog(self, qml_name):
|
||||
Logger.log("d", "Creating dialog [%s]", qml_name)
|
||||
|
@ -195,6 +181,8 @@ class SliceInfo(QObject, Extension):
|
|||
model = dict()
|
||||
model["hash"] = node.getMeshData().getHash()
|
||||
bounding_box = node.getBoundingBox()
|
||||
if not bounding_box:
|
||||
continue
|
||||
model["bounding_box"] = {"minimum": {"x": bounding_box.minimum.x,
|
||||
"y": bounding_box.minimum.y,
|
||||
"z": bounding_box.minimum.z},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.View.View import View
|
||||
|
@ -7,7 +7,6 @@ from UM.Scene.Selection import Selection
|
|||
from UM.Resources import Resources
|
||||
from UM.Application import Application
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.Settings.Validator import ValidatorState
|
||||
from UM.Math.Color import Color
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
|
@ -20,9 +19,9 @@ import math
|
|||
class SolidView(View):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
Application.getInstance().getPreferences().addPreference("view/show_overhang", True)
|
||||
|
||||
application = Application.getInstance()
|
||||
application.getPreferences().addPreference("view/show_overhang", True)
|
||||
application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
self._enabled_shader = None
|
||||
self._disabled_shader = None
|
||||
self._non_printing_shader = None
|
||||
|
@ -30,6 +29,38 @@ class SolidView(View):
|
|||
|
||||
self._extruders_model = None
|
||||
self._theme = None
|
||||
self._support_angle = 90
|
||||
|
||||
self._global_stack = None
|
||||
|
||||
Application.getInstance().engineCreatedSignal.connect(self._onGlobalContainerChanged)
|
||||
|
||||
def _onGlobalContainerChanged(self) -> None:
|
||||
if self._global_stack:
|
||||
try:
|
||||
self._global_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
except TypeError:
|
||||
pass
|
||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
||||
self._global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
self._onPropertyChanged("support_angle", "value") # Force an re-evaluation
|
||||
|
||||
def _onPropertyChanged(self, key: str, property_name: str) -> None:
|
||||
if key != "support_angle" or property_name != "value":
|
||||
return
|
||||
# As the rendering is called a *lot* we really, dont want to re-evaluate the property every time. So we store em!
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
|
||||
support_angle_stack = global_container_stack.extruders.get(str(support_extruder_nr))
|
||||
if support_angle_stack:
|
||||
self._support_angle = support_angle_stack.getProperty("support_angle", "value")
|
||||
|
||||
def beginRendering(self):
|
||||
scene = self.getController().getScene()
|
||||
|
@ -63,14 +94,10 @@ class SolidView(View):
|
|||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
|
||||
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)
|
||||
|
||||
if support_angle_stack is not None and Application.getInstance().getPreferences().getValue("view/show_overhang"):
|
||||
angle = support_angle_stack.getProperty("support_angle", "value")
|
||||
if Application.getInstance().getPreferences().getValue("view/show_overhang"):
|
||||
# Make sure the overhang angle is valid before passing it to the shader
|
||||
if angle is not None and angle >= 0 and angle <= 90:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle)))
|
||||
if self._support_angle is not None and self._support_angle >= 0 and self._support_angle <= 90:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - self._support_angle)))
|
||||
else:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.
|
||||
else:
|
||||
|
|
|
@ -65,6 +65,7 @@ Item
|
|||
{
|
||||
id: description
|
||||
text: details.description || ""
|
||||
font: UM.Theme.getFont("default")
|
||||
anchors
|
||||
{
|
||||
top: title.bottom
|
||||
|
|
|
@ -26,7 +26,7 @@ UM.Dialog
|
|||
minimumWidth: 450 * screenScaleFactor
|
||||
minimumHeight: 150 * screenScaleFactor
|
||||
|
||||
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal
|
||||
modality: Qt.WindowModal
|
||||
|
||||
Column
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ import Cura 1.1 as Cura
|
|||
Column
|
||||
{
|
||||
property bool installed: toolbox.isInstalled(model.id)
|
||||
property bool canUpdate: toolbox.canUpdate(model.id)
|
||||
property bool canUpdate: CuraApplication.getPackageManager().packagesWithUpdate.indexOf(model.id) != -1
|
||||
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
|
||||
property var packageData
|
||||
|
||||
|
@ -112,11 +112,9 @@ Column
|
|||
{
|
||||
target: toolbox
|
||||
onInstallChanged: installed = toolbox.isInstalled(model.id)
|
||||
onMetadataChanged: canUpdate = toolbox.canUpdate(model.id)
|
||||
onFilterChanged:
|
||||
{
|
||||
installed = toolbox.isInstalled(model.id)
|
||||
canUpdate = toolbox.canUpdate(model.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ Item
|
|||
height: (parent.height * 0.4) | 0
|
||||
anchors
|
||||
{
|
||||
bottom: parent.bottomcommi
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
sourceSize.height: height
|
||||
|
|
|
@ -14,7 +14,7 @@ Rectangle
|
|||
Column
|
||||
{
|
||||
height: childrenRect.height + 2 * padding
|
||||
spacing: UM.Theme.getSize("toolbox_showcase_spacing").width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
width: parent.width
|
||||
padding: UM.Theme.getSize("wide_margin").height
|
||||
Label
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
import UM 1.4 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
|
@ -50,6 +52,7 @@ Item
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxTabButton
|
||||
{
|
||||
id: installedTabButton
|
||||
|
@ -62,7 +65,25 @@ Item
|
|||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
onClicked: toolbox.viewCategory = "installed"
|
||||
width: UM.Theme.getSize("toolbox_header_tab").width + marketplaceNotificationIcon.width - UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Cura.NotificationIcon
|
||||
{
|
||||
id: marketplaceNotificationIcon
|
||||
|
||||
visible: CuraApplication.getPackageManager().packagesWithUpdate.length > 0
|
||||
|
||||
anchors.right: installedTabButton.right
|
||||
anchors.verticalCenter: installedTabButton.verticalCenter
|
||||
|
||||
labelText:
|
||||
{
|
||||
const itemCount = CuraApplication.getPackageManager().packagesWithUpdate.length
|
||||
return itemCount > 9 ? "9+" : itemCount
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxShadow
|
||||
{
|
||||
anchors.top: bar.bottom
|
||||
|
|
|
@ -10,7 +10,7 @@ import Cura 1.1 as Cura
|
|||
|
||||
Column
|
||||
{
|
||||
property bool canUpdate: false
|
||||
property bool canUpdate: CuraApplication.getPackageManager().packagesWithUpdate.indexOf(model.id) != -1
|
||||
property bool canDowngrade: false
|
||||
property bool loginRequired: model.login_required && !Cura.API.account.isLoggedIn
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
|
@ -83,7 +83,6 @@ Column
|
|||
target: toolbox
|
||||
onMetadataChanged:
|
||||
{
|
||||
canUpdate = toolbox.canUpdate(model.id)
|
||||
canDowngrade = toolbox.canDowngrade(model.id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,14 +9,17 @@ Button
|
|||
{
|
||||
id: control
|
||||
property bool active: false
|
||||
hoverEnabled: true
|
||||
|
||||
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
|
||||
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
|
||||
|
||||
background: Item
|
||||
{
|
||||
implicitWidth: UM.Theme.getSize("toolbox_header_tab").width
|
||||
implicitHeight: UM.Theme.getSize("toolbox_header_tab").height
|
||||
id: backgroundItem
|
||||
Rectangle
|
||||
{
|
||||
id: highlight
|
||||
|
||||
visible: control.active
|
||||
color: UM.Theme.getColor("primary")
|
||||
anchors.bottom: parent.bottom
|
||||
|
@ -24,28 +27,42 @@ Button
|
|||
height: UM.Theme.getSize("toolbox_header_highlight").height
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
id: label
|
||||
text: control.text
|
||||
color:
|
||||
{
|
||||
if(control.hovered)
|
||||
{
|
||||
return UM.Theme.getColor("toolbox_header_button_text_hovered");
|
||||
}
|
||||
if(control.active)
|
||||
{
|
||||
return UM.Theme.getColor("toolbox_header_button_text_active");
|
||||
}
|
||||
else
|
||||
{
|
||||
return UM.Theme.getColor("toolbox_header_button_text_inactive");
|
||||
}
|
||||
}
|
||||
font: control.enabled ? (control.active ? UM.Theme.getFont("medium_bold") : UM.Theme.getFont("medium")) : UM.Theme.getFont("default_italic")
|
||||
color: UM.Theme.getColor("toolbox_header_button_text_inactive")
|
||||
font: UM.Theme.getFont("medium")
|
||||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
states:
|
||||
[
|
||||
State
|
||||
{
|
||||
name: "disabled"
|
||||
when: !control.enabled
|
||||
PropertyChanges
|
||||
{
|
||||
target: label
|
||||
font: UM.Theme.getFont("default_italic")
|
||||
}
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "active"
|
||||
when: control.active
|
||||
PropertyChanges
|
||||
{
|
||||
target: label
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("action_button_text")
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -53,7 +53,7 @@ class AuthorsModel(ListModel):
|
|||
|
||||
# Filter on all the key-word arguments.
|
||||
for key, value in self._filter.items():
|
||||
if key is "package_types":
|
||||
if key == "package_types":
|
||||
key_filter = lambda item, value = value: value in item["package_types"] # type: ignore
|
||||
elif "*" in value:
|
||||
key_filter = lambda item, key = key, value = value: self._matchRegExp(item, key, value) # type: ignore
|
||||
|
|
|
@ -112,7 +112,7 @@ class PackagesModel(ListModel):
|
|||
|
||||
# Filter on all the key-word arguments.
|
||||
for key, value in self._filter.items():
|
||||
if key is "tags":
|
||||
if key == "tags":
|
||||
key_filter = lambda item, v = value: v in item["tags"]
|
||||
elif "*" in value:
|
||||
key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v)
|
||||
|
|
|
@ -50,7 +50,6 @@ class Toolbox(QObject, Extension):
|
|||
self._request_headers = [] # type: List[Tuple[bytes, bytes]]
|
||||
self._updateRequestHeader()
|
||||
|
||||
|
||||
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 = set() # type: Set[str]
|
||||
|
@ -106,6 +105,7 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader)
|
||||
self._application.getCuraAPI().account.accessTokenChanged.connect(self._updateRequestHeader)
|
||||
|
||||
# Signals:
|
||||
# --------------------------------------------------------------------------
|
||||
|
@ -190,8 +190,10 @@ class Toolbox(QObject, Extension):
|
|||
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url))
|
||||
}
|
||||
|
||||
@pyqtSlot()
|
||||
def browsePackages(self) -> None:
|
||||
# Request the latest and greatest!
|
||||
self._fetchPackageData()
|
||||
|
||||
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.
|
||||
|
@ -209,6 +211,10 @@ class Toolbox(QObject, Extension):
|
|||
# Gather installed packages:
|
||||
self._updateInstalledModels()
|
||||
|
||||
@pyqtSlot()
|
||||
def browsePackages(self) -> None:
|
||||
self._fetchPackageData()
|
||||
|
||||
if not self._dialog:
|
||||
self._dialog = self._createDialog("Toolbox.qml")
|
||||
|
||||
|
@ -455,36 +461,6 @@ class Toolbox(QObject, Extension):
|
|||
break
|
||||
return remote_package
|
||||
|
||||
# Checks
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtSlot(str, result = bool)
|
||||
def canUpdate(self, package_id: str) -> bool:
|
||||
local_package = self._package_manager.getInstalledPackageInfo(package_id)
|
||||
if local_package is None:
|
||||
local_package = self.getOldPluginPackageMetadata(package_id)
|
||||
if local_package is None:
|
||||
return False
|
||||
|
||||
remote_package = self.getRemotePackage(package_id)
|
||||
if remote_package is None:
|
||||
return False
|
||||
|
||||
local_version = Version(local_package["package_version"])
|
||||
remote_version = Version(remote_package["package_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:
|
||||
# First read sdk_version_semver. If that doesn't exist, read just sdk_version (old version system).
|
||||
remote_sdk_version = Version(remote_package.get("sdk_version_semver", remote_package.get("sdk_version", 0)))
|
||||
local_sdk_version = Version(local_package.get("sdk_version_semver", local_package.get("sdk_version", 0)))
|
||||
can_upgrade = local_sdk_version < remote_sdk_version
|
||||
|
||||
return can_upgrade
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def canDowngrade(self, package_id: str) -> bool:
|
||||
# If the currently installed version is higher than the bundled version (if present), the we can downgrade
|
||||
|
@ -584,9 +560,15 @@ class Toolbox(QObject, Extension):
|
|||
if self._download_reply:
|
||||
try:
|
||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||
except TypeError: # Raised when the method is not connected to the signal yet.
|
||||
except (TypeError, RuntimeError): # Raised when the method is not connected to the signal yet.
|
||||
pass # Don't need to disconnect.
|
||||
self._download_reply.abort()
|
||||
try:
|
||||
self._download_reply.abort()
|
||||
except RuntimeError:
|
||||
# In some cases the garbage collector is a bit to agressive, which causes the dowload_reply
|
||||
# to be deleted (especially if the machine has been put to sleep). As we don't know what exactly causes
|
||||
# this (The issue probably lives in the bowels of (py)Qt somewhere), we can only catch and ignore it.
|
||||
pass
|
||||
self._download_reply = None
|
||||
self._download_request = None
|
||||
self.setDownloadProgress(0)
|
||||
|
@ -632,11 +614,12 @@ class Toolbox(QObject, Extension):
|
|||
self._server_response_data[response_type] = json_data["data"]
|
||||
self._models[response_type].setMetadata(self._server_response_data[response_type])
|
||||
|
||||
if response_type is "packages":
|
||||
if response_type == "packages":
|
||||
self._models[response_type].setFilter({"type": "plugin"})
|
||||
self.reBuildMaterialsModels()
|
||||
self.reBuildPluginsModels()
|
||||
elif response_type is "authors":
|
||||
self._notifyPackageManager()
|
||||
elif response_type == "authors":
|
||||
self._models[response_type].setFilter({"package_types": "material"})
|
||||
self._models[response_type].setFilter({"tags": "generic"})
|
||||
|
||||
|
@ -656,6 +639,11 @@ class Toolbox(QObject, Extension):
|
|||
# Ignore any operation that is not a get operation
|
||||
pass
|
||||
|
||||
# This function goes through all known remote versions of a package and notifies the package manager of this change
|
||||
def _notifyPackageManager(self):
|
||||
for package in self._server_response_data["packages"]:
|
||||
self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]))
|
||||
|
||||
def _onDownloadProgress(self, bytes_sent: int, bytes_total: int) -> None:
|
||||
if bytes_total > 0:
|
||||
new_progress = bytes_sent / bytes_total * 100
|
||||
|
|
42
plugins/UFPReader/UFPReader.py
Normal file
42
plugins/UFPReader/UFPReader.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from Charon.VirtualFile import VirtualFile
|
||||
|
||||
from UM.Mesh.MeshReader import MeshReader
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
|
||||
class UFPReader(MeshReader):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ufp",
|
||||
comment = "Ultimaker Format Package",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
self._supported_extensions = [".ufp"]
|
||||
|
||||
def _read(self, file_name: str) -> "CuraSceneNode":
|
||||
# Open the file
|
||||
archive = VirtualFile()
|
||||
archive.open(file_name)
|
||||
# Get the gcode data from the file
|
||||
gcode_data = archive.getData("/3D/model.gcode")
|
||||
# Convert the bytes stream to string
|
||||
gcode_stream = gcode_data["/3D/model.gcode"].decode("utf-8")
|
||||
|
||||
# Open the GCodeReader to parse the data
|
||||
gcode_reader = PluginRegistry.getInstance().getPluginObject("GCodeReader") # type: ignore
|
||||
gcode_reader.preReadFromStream(gcode_stream) # type: ignore
|
||||
return gcode_reader.readFromStream(gcode_stream) # type: ignore
|
26
plugins/UFPReader/__init__.py
Normal file
26
plugins/UFPReader/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
#Copyright (c) 2019 Ultimaker B.V.
|
||||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from . import UFPReader
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"mesh_reader": [
|
||||
{
|
||||
"mime_type": "application/x-ufp",
|
||||
"extension": "ufp",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Ultimaker Format Package")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
app.addNonSliceableExtension(".ufp")
|
||||
return {"mesh_reader": UFPReader.UFPReader()}
|
||||
|
8
plugins/UFPReader/plugin.json
Normal file
8
plugins/UFPReader/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "UFP Reader",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading Ultimaker Format Packages.",
|
||||
"supported_sdk_versions": ["6.0.0"],
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -28,7 +28,7 @@ class UFPWriter(MeshWriter):
|
|||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ufp",
|
||||
comment = "Cura UFP File",
|
||||
comment = "Ultimaker Format Package",
|
||||
suffixes = ["ufp"]
|
||||
)
|
||||
)
|
||||
|
|
|
@ -371,7 +371,7 @@ Cura.MachineAction
|
|||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@alabel", "Enter the IP address or hostname of your printer on the network.")
|
||||
text: catalog.i18nc("@label", "Enter the IP address or hostname of your printer on the network.")
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
renderType: Text.NativeRendering
|
||||
|
|
|
@ -158,14 +158,9 @@ Item
|
|||
spacing: 6 // TODO: Theme!
|
||||
visible: printJob
|
||||
|
||||
Repeater
|
||||
MonitorPrinterPill
|
||||
{
|
||||
id: compatiblePills
|
||||
delegate: MonitorPrinterPill
|
||||
{
|
||||
text: modelData
|
||||
}
|
||||
model: printJob ? printJob.compatibleMachineFamilies : []
|
||||
text: printJob.configuration.printerType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ Item
|
|||
width: childrenRect.width
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
|
||||
ProgressBar
|
||||
UM.ProgressBar
|
||||
{
|
||||
id: progressBar
|
||||
anchors
|
||||
|
@ -30,22 +30,6 @@ Item
|
|||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
value: printJob ? printJob.progress : 0
|
||||
style: ProgressBarStyle
|
||||
{
|
||||
background: Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("monitor_progress_bar_empty")
|
||||
implicitHeight: visible ? 12 * screenScaleFactor : 0 // TODO: Theme!
|
||||
implicitWidth: 180 * screenScaleFactor // TODO: Theme!
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
progress: Rectangle
|
||||
{
|
||||
id: progressItem;
|
||||
color: printJob && printJob.isActive ? UM.Theme.getColor("monitor_progress_bar_fill") : UM.Theme.getColor("monitor_progress_bar_deactive")
|
||||
radius: 2 * screenScaleFactor // TODO: Theme!
|
||||
}
|
||||
}
|
||||
}
|
||||
Label
|
||||
{
|
||||
|
|
|
@ -140,7 +140,7 @@ Item
|
|||
{
|
||||
id: printerConfiguration
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
buildplate: printer ? "Glass" : null // 'Glass' as a default
|
||||
buildplate: printer ? catalog.i18nc("@label", "Glass") : null // 'Glass' as a default
|
||||
configurations:
|
||||
{
|
||||
var configs = []
|
||||
|
|
|
@ -210,7 +210,7 @@ Item
|
|||
|
||||
Label
|
||||
{
|
||||
text: "All jobs are printed."
|
||||
text: i18n.i18nc("@info", "All jobs are printed.")
|
||||
color: UM.Theme.getColor("monitor_text_primary")
|
||||
font: UM.Theme.getFont("medium") // 14pt, regular
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ from UM.Scene.SceneNode import SceneNode
|
|||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
|
||||
from .CloudOutputController import CloudOutputController
|
||||
from ..MeshFormatHandler import MeshFormatHandler
|
||||
|
@ -58,6 +58,14 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
# Therefore we create a private signal used to trigger the printersChanged signal.
|
||||
_clusterPrintersChanged = pyqtSignal()
|
||||
|
||||
# Map of Cura Connect machine_variant field to Cura machine types.
|
||||
# Needed for printer discovery stack creation.
|
||||
_host_machine_variant_to_machine_type_map = {
|
||||
"Ultimaker 3": "ultimaker3",
|
||||
"Ultimaker 3 Extended": "ultimaker3_extended",
|
||||
"Ultimaker S5": "ultimaker_s5"
|
||||
}
|
||||
|
||||
## Creates a new cloud output device
|
||||
# \param api_client: The client that will run the API calls
|
||||
# \param cluster: The device response received from the cloud API.
|
||||
|
@ -68,10 +76,10 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
# Because the cloud connection does not off all of these, we manually construct this version here.
|
||||
# An example of why this is needed is the selection of the compatible file type when exporting the tool path.
|
||||
properties = {
|
||||
b"address": b"",
|
||||
b"name": cluster.host_name.encode() if cluster.host_name else b"",
|
||||
b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"",
|
||||
b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"",
|
||||
b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
|
||||
b"printer_type": b""
|
||||
b"cluster_size": b"1" # cloud devices are always clusters of at least one
|
||||
}
|
||||
|
||||
super().__init__(device_id = cluster.cluster_id, address = "",
|
||||
|
@ -85,16 +93,18 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
# We use the Cura Connect monitor tab to get most functionality right away.
|
||||
if PluginRegistry.getInstance() is not None:
|
||||
self._monitor_view_qml_path = os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "qml", "MonitorStage.qml"
|
||||
)
|
||||
plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting")
|
||||
if plugin_path is None:
|
||||
Logger.log("e", "Cloud not find plugin path for plugin UM3NetworkPrnting")
|
||||
raise RuntimeError("Cloud not find plugin path for plugin UM3NetworkPrnting")
|
||||
self._monitor_view_qml_path = os.path.join(plugin_path, "resources", "qml", "MonitorStage.qml")
|
||||
|
||||
# Trigger the printersChanged signal when the private signal is triggered.
|
||||
self.printersChanged.connect(self._clusterPrintersChanged)
|
||||
|
||||
# We keep track of which printer is visible in the monitor page.
|
||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||
self._host_machine_type = ""
|
||||
|
||||
# Properties to populate later on with received cloud data.
|
||||
self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
|
||||
|
@ -145,9 +155,17 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
## Checks whether the given network key is found in the cloud's host name
|
||||
def matchesNetworkKey(self, network_key: str) -> bool:
|
||||
# A network key looks like "ultimakersystem-aabbccdd0011._ultimaker._tcp.local."
|
||||
# Typically, a network key looks like "ultimakersystem-aabbccdd0011._ultimaker._tcp.local."
|
||||
# the host name should then be "ultimakersystem-aabbccdd0011"
|
||||
return network_key.startswith(self.clusterData.host_name)
|
||||
if network_key.startswith(self.clusterData.host_name):
|
||||
return True
|
||||
|
||||
# However, for manually added printers, the local IP address is used in lieu of a proper
|
||||
# network key, so check for that as well
|
||||
if self.clusterData.host_internal_ip is not None and network_key.find(self.clusterData.host_internal_ip):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
## Set all the interface elements and texts for this output device.
|
||||
def _setInterfaceElements(self) -> None:
|
||||
|
@ -227,6 +245,10 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
|
||||
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus]
|
||||
|
||||
if len(printers) > 0:
|
||||
# We need the machine type of the host (1st list entry) to allow discovery to work.
|
||||
self._host_machine_type = printers[0].machine_variant
|
||||
|
||||
removed_printers, added_printers, updated_printers = findChanges(previous, received)
|
||||
|
||||
for removed_printer in removed_printers:
|
||||
|
@ -350,6 +372,19 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
).show()
|
||||
self.writeFinished.emit()
|
||||
|
||||
## Gets the printer type of the cluster host. Falls back to the printer type in the device properties.
|
||||
@pyqtProperty(str, notify=_clusterPrintersChanged)
|
||||
def printerType(self) -> str:
|
||||
if self._printers and self._host_machine_type in self._host_machine_variant_to_machine_type_map:
|
||||
return self._host_machine_variant_to_machine_type_map[self._host_machine_type]
|
||||
return super().printerType
|
||||
|
||||
## Gets the number of printers in the cluster.
|
||||
# We use a minimum of 1 because cloud devices are always a cluster and printer discovery needs it.
|
||||
@pyqtProperty(int, notify = _clusterPrintersChanged)
|
||||
def clusterSize(self) -> int:
|
||||
return max(1, len(self._printers))
|
||||
|
||||
## Gets the remote printers.
|
||||
@pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
|
||||
def printers(self) -> List[PrinterOutputModel]:
|
||||
|
@ -367,10 +402,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._active_printer = printer
|
||||
self.activePrinterChanged.emit()
|
||||
|
||||
@pyqtProperty(int, notify = _clusterPrintersChanged)
|
||||
def clusterSize(self) -> int:
|
||||
return len(self._printers)
|
||||
|
||||
## Get remote print jobs.
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def printJobs(self) -> List[UM3PrintJobOutputModel]:
|
||||
|
|
|
@ -7,7 +7,7 @@ from PyQt5.QtCore import QTimer
|
|||
from UM import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from UM.Signal import Signal
|
||||
from cura.API import Account
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
@ -81,25 +81,61 @@ class CloudOutputDeviceManager:
|
|||
Logger.log("d", "Removed: %s, added: %s, updates: %s", len(removed_devices), len(added_clusters), len(updates))
|
||||
|
||||
# Remove output devices that are gone
|
||||
for removed_cluster in removed_devices:
|
||||
if removed_cluster.isConnected():
|
||||
removed_cluster.disconnect()
|
||||
removed_cluster.close()
|
||||
self._output_device_manager.removeOutputDevice(removed_cluster.key)
|
||||
self.removedCloudCluster.emit()
|
||||
del self._remote_clusters[removed_cluster.key]
|
||||
for device in removed_devices:
|
||||
if device.isConnected():
|
||||
device.disconnect()
|
||||
device.close()
|
||||
self._output_device_manager.removeOutputDevice(device.key)
|
||||
self._application.getDiscoveredPrintersModel().removeDiscoveredPrinter(device.key)
|
||||
self.removedCloudCluster.emit(device)
|
||||
del self._remote_clusters[device.key]
|
||||
|
||||
# Add an output device for each new remote cluster.
|
||||
# We only add when is_online as we don't want the option in the drop down if the cluster is not online.
|
||||
for added_cluster in added_clusters:
|
||||
device = CloudOutputDevice(self._api, added_cluster)
|
||||
self._remote_clusters[added_cluster.cluster_id] = device
|
||||
self.addedCloudCluster.emit()
|
||||
for cluster in added_clusters:
|
||||
device = CloudOutputDevice(self._api, cluster)
|
||||
self._remote_clusters[cluster.cluster_id] = device
|
||||
self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(
|
||||
cluster.cluster_id,
|
||||
device.key,
|
||||
cluster.friendly_name,
|
||||
self._createMachineFromDiscoveredPrinter,
|
||||
device.printerType,
|
||||
device
|
||||
)
|
||||
self.addedCloudCluster.emit(cluster)
|
||||
|
||||
# Update the output devices
|
||||
for device, cluster in updates:
|
||||
device.clusterData = cluster
|
||||
self._application.getDiscoveredPrintersModel().updateDiscoveredPrinter(
|
||||
cluster.cluster_id,
|
||||
cluster.friendly_name,
|
||||
device.printerType,
|
||||
)
|
||||
|
||||
self._connectToActiveMachine()
|
||||
|
||||
def _createMachineFromDiscoveredPrinter(self, key: str) -> None:
|
||||
device = self._remote_clusters[key] # type: CloudOutputDevice
|
||||
if not device:
|
||||
Logger.log("e", "Could not find discovered device with key [%s]", key)
|
||||
return
|
||||
|
||||
group_name = device.clusterData.friendly_name
|
||||
machine_type_id = device.printerType
|
||||
|
||||
Logger.log("i", "Creating machine from cloud device with key = [%s], group name = [%s], printer type = [%s]",
|
||||
key, group_name, machine_type_id)
|
||||
|
||||
# The newly added machine is automatically activated.
|
||||
self._application.getMachineManager().addMachine(machine_type_id, group_name)
|
||||
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not active_machine:
|
||||
return
|
||||
|
||||
active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
|
||||
self._connectToOutputDevice(device, active_machine)
|
||||
|
||||
## Callback for when the active machine was changed by the user or a new remote cluster was found.
|
||||
def _connectToActiveMachine(self) -> None:
|
||||
|
|
|
@ -11,8 +11,8 @@ I18N_CATALOG = i18nCatalog("cura")
|
|||
class CloudProgressMessage(Message):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
text = I18N_CATALOG.i18nc("@info:status", "Sending data to remote cluster"),
|
||||
title = I18N_CATALOG.i18nc("@info:status", "Sending data to remote cluster"),
|
||||
title = I18N_CATALOG.i18nc("@info:status", "Sending Print Job"),
|
||||
text = I18N_CATALOG.i18nc("@info:status", "Uploading via Ultimaker Cloud"),
|
||||
progress = -1,
|
||||
lifetime = 0,
|
||||
dismissable = False,
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Union, Dict, Optional, Any
|
||||
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
from cura.PrinterOutput.Models.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
from cura.PrinterOutput.Models.ExtruderOutputModel import ExtruderOutputModel
|
||||
from .CloudClusterPrinterConfigurationMaterial import CloudClusterPrinterConfigurationMaterial
|
||||
from .BaseCloudModel import BaseCloudModel
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import List, Optional, Union, Dict, Any
|
||||
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
|
||||
from ...UM3PrintJobOutputModel import UM3PrintJobOutputModel
|
||||
from ...ConfigurationChangeModel import ConfigurationChangeModel
|
||||
from ..CloudOutputController import CloudOutputController
|
||||
|
@ -95,9 +95,9 @@ class CloudClusterPrintJobStatus(BaseCloudModel):
|
|||
return model
|
||||
|
||||
## Creates a new configuration model
|
||||
def _createConfigurationModel(self) -> ConfigurationModel:
|
||||
def _createConfigurationModel(self) -> PrinterConfigurationModel:
|
||||
extruders = [extruder.createConfigurationModel() for extruder in self.configuration or ()]
|
||||
configuration = ConfigurationModel()
|
||||
configuration = PrinterConfigurationModel()
|
||||
configuration.setExtruderConfigurations(extruders)
|
||||
return configuration
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from typing import Optional
|
|||
|
||||
from UM.Logger import Logger
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||
from cura.PrinterOutput.Models.MaterialOutputModel import MaterialOutputModel
|
||||
from .BaseCloudModel import BaseCloudModel
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from typing import List, Union, Dict, Optional, Any
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
from .CloudClusterBuildPlate import CloudClusterBuildPlate
|
||||
from .CloudClusterPrintCoreConfiguration import CloudClusterPrintCoreConfiguration
|
||||
from .BaseCloudModel import BaseCloudModel
|
||||
|
|
|
@ -16,13 +16,16 @@ class CloudClusterResponse(BaseCloudModel):
|
|||
# \param status: The status of the cluster authentication (active or inactive).
|
||||
# \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
|
||||
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
|
||||
host_version: Optional[str] = None, **kwargs) -> None:
|
||||
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
|
||||
friendly_name: Optional[str] = None, **kwargs) -> None:
|
||||
self.cluster_id = cluster_id
|
||||
self.host_guid = host_guid
|
||||
self.host_name = host_name
|
||||
self.status = status
|
||||
self.is_online = is_online
|
||||
self.host_version = host_version
|
||||
self.host_internal_ip = host_internal_ip
|
||||
self.friendly_name = friendly_name
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Validates the model, raising an exception if the model is invalid.
|
||||
|
|
|
@ -19,12 +19,12 @@ from UM.Scene.SceneNode import SceneNode # For typing.
|
|||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
from cura.PrinterOutput.Models.PrinterConfigurationModel import PrinterConfigurationModel
|
||||
from cura.PrinterOutput.Models.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState, NetworkedPrinterOutputDevice
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.Models.MaterialOutputModel import MaterialOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
|
||||
from .Cloud.Utils import formatTimeCompleted, formatDateCompleted
|
||||
from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
|
||||
|
@ -66,10 +66,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._received_print_jobs = False # type: bool
|
||||
|
||||
if PluginRegistry.getInstance() is not None:
|
||||
self._monitor_view_qml_path = os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "qml", "MonitorStage.qml"
|
||||
)
|
||||
plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting")
|
||||
if plugin_path is None:
|
||||
Logger.log("e", "Cloud not find plugin path for plugin UM3NetworkPrnting")
|
||||
raise RuntimeError("Cloud not find plugin path for plugin UM3NetworkPrnting")
|
||||
self._monitor_view_qml_path = os.path.join(plugin_path, "resources", "qml", "MonitorStage.qml")
|
||||
|
||||
# Trigger the printersChanged signal when the private signal is triggered
|
||||
self.printersChanged.connect(self._clusterPrintersChanged)
|
||||
|
@ -395,9 +396,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
newly_finished_jobs = [job for job in finished_jobs if job not in self._finished_jobs and job.owner == username]
|
||||
for job in newly_finished_jobs:
|
||||
if job.assignedPrinter:
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.".format(printer_name=job.assignedPrinter.name, job_name = job.name))
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.").format(printer_name=job.assignedPrinter.name, job_name = job.name)
|
||||
else:
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "The print job '{job_name}' was finished.".format(job_name = job.name))
|
||||
job_completed_text = i18n_catalog.i18nc("@info:status", "The print job '{job_name}' was finished.").format(job_name = job.name)
|
||||
job_completed_message = Message(text=job_completed_text, title = i18n_catalog.i18nc("@info:status", "Print finished"))
|
||||
job_completed_message.show()
|
||||
|
||||
|
@ -522,7 +523,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
print_job = UM3PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
||||
key=data["uuid"], name= data["name"])
|
||||
|
||||
configuration = ConfigurationModel()
|
||||
configuration = PrinterConfigurationModel()
|
||||
extruders = [ExtruderConfigurationModel(position = idx) for idx in range(0, self._number_of_extruders)]
|
||||
for index in range(0, self._number_of_extruders):
|
||||
try:
|
||||
|
@ -534,6 +535,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
extruder.setMaterial(self._createMaterialOutputModel(extruder_data.get("material", {})))
|
||||
|
||||
configuration.setExtruderConfigurations(extruders)
|
||||
configuration.setPrinterType(data.get("machine_variant", ""))
|
||||
print_job.updateConfiguration(configuration)
|
||||
print_job.setCompatibleMachineFamilies(data.get("compatible_machine_families", []))
|
||||
print_job.stateChanged.connect(self._printJobStateChanged)
|
||||
|
@ -633,6 +635,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
printer.updateKey(data["uuid"])
|
||||
printer.updateType(data["machine_variant"])
|
||||
|
||||
if data["status"] != "unreachable":
|
||||
self._application.getDiscoveredPrintersModel().updateDiscoveredPrinter(data["ip_address"],
|
||||
name = data["friendly_name"],
|
||||
machine_type = data["machine_variant"])
|
||||
|
||||
# Do not store the build plate information that comes from connect if the current printer has not build plate information
|
||||
if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
|
||||
printer.updateBuildplate(data["build_plate"]["type"])
|
||||
|
|
|
@ -5,7 +5,7 @@ from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
|||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||
|
||||
class ClusterUM3PrinterOutputController(PrinterOutputController):
|
||||
def __init__(self, output_device):
|
||||
|
|
|
@ -18,7 +18,7 @@ from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
|||
from .UM3OutputDevicePlugin import UM3OutputDevicePlugin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -41,6 +41,11 @@ class DiscoverUM3Action(MachineAction):
|
|||
# Time to wait after a zero-conf service change before allowing a zeroconf reset
|
||||
self._zero_conf_change_grace_period = 0.25 #type: float
|
||||
|
||||
# Overrides the one in MachineAction.
|
||||
# This requires not attention from the user (any more), so we don't need to show any 'upgrade screens'.
|
||||
def needsUserInteraction(self) -> bool:
|
||||
return False
|
||||
|
||||
@pyqtSlot()
|
||||
def startDiscovery(self):
|
||||
if not self._network_plugin:
|
||||
|
@ -110,7 +115,7 @@ class DiscoverUM3Action(MachineAction):
|
|||
previous_connect_group_name = meta_data["group_name"]
|
||||
global_container_stack.setMetaDataEntry("group_name", group_name)
|
||||
# Find all the places where there is the same group name and change it accordingly
|
||||
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name)
|
||||
self._replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name)
|
||||
else:
|
||||
global_container_stack.setMetaDataEntry("group_name", group_name)
|
||||
# Set the default value for "hidden", which is used when you have a group with multiple types of printers
|
||||
|
@ -118,49 +123,21 @@ class DiscoverUM3Action(MachineAction):
|
|||
|
||||
if self._network_plugin:
|
||||
# Ensure that the connection states are refreshed.
|
||||
self._network_plugin.reCheckConnections()
|
||||
self._network_plugin.refreshConnections()
|
||||
|
||||
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
|
||||
def _replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
|
||||
machines = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
|
||||
for machine in machines:
|
||||
if machine.getMetaDataEntry(key) == value:
|
||||
machine.setMetaDataEntry(key, new_value)
|
||||
|
||||
# Associates the currently active machine with the given printer device. The network connection information will be
|
||||
# stored into the metadata of the currently active machine.
|
||||
@pyqtSlot(QObject)
|
||||
def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None:
|
||||
if not printer_device:
|
||||
return
|
||||
|
||||
Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key)
|
||||
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return
|
||||
|
||||
meta_data = global_container_stack.getMetaData()
|
||||
|
||||
if "um_network_key" in meta_data: # Global stack already had a connection, but it's changed.
|
||||
old_network_key = meta_data["um_network_key"]
|
||||
# Since we might have a bunch of hidden stacks, we also need to change it there.
|
||||
metadata_filter = {"um_network_key": old_network_key}
|
||||
containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter)
|
||||
|
||||
for container in containers:
|
||||
container.setMetaDataEntry("um_network_key", printer_device.key)
|
||||
|
||||
# Delete old authentication data.
|
||||
Logger.log("d", "Removing old authentication id %s for device %s",
|
||||
global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key)
|
||||
|
||||
container.removeMetaDataEntry("network_authentication_id")
|
||||
container.removeMetaDataEntry("network_authentication_key")
|
||||
|
||||
# Ensure that these containers do know that they are configured for network connection
|
||||
container.addConfiguredConnectionType(printer_device.connectionType.value)
|
||||
|
||||
else: # Global stack didn't have a connection yet, configure it.
|
||||
global_container_stack.setMetaDataEntry("um_network_key", printer_device.key)
|
||||
global_container_stack.addConfiguredConnectionType(printer_device.connectionType.value)
|
||||
|
||||
if self._network_plugin:
|
||||
# Ensure that the connection states are refreshed.
|
||||
self._network_plugin.reCheckConnections()
|
||||
self._network_plugin.associateActiveMachineWithPrinterDevice(printer_device)
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getStoredKey(self) -> str:
|
||||
|
@ -180,7 +157,9 @@ class DiscoverUM3Action(MachineAction):
|
|||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def existsKey(self, key: str) -> bool:
|
||||
return CuraApplication.getInstance().getMachineManager().existNetworkInstances(network_key = key)
|
||||
metadata_filter = {"um_network_key": key}
|
||||
containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter)
|
||||
return bool(containers)
|
||||
|
||||
@pyqtSlot()
|
||||
def loadConfigurationFromPrinter(self) -> None:
|
||||
|
|
|
@ -2,10 +2,10 @@ from typing import List, Optional
|
|||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.Models.MaterialOutputModel import MaterialOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
|
||||
from cura.Settings.ContainerManager import ContainerManager
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
@ -77,13 +77,15 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self.setIconName("print")
|
||||
|
||||
if PluginRegistry.getInstance() is not None:
|
||||
self._output_controller = LegacyUM3PrinterOutputController(self)
|
||||
|
||||
def _createMonitorViewFromQML(self) -> None:
|
||||
if self._monitor_view_qml_path is None and PluginRegistry.getInstance() is not None:
|
||||
self._monitor_view_qml_path = os.path.join(
|
||||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "qml", "MonitorStage.qml"
|
||||
)
|
||||
|
||||
self._output_controller = LegacyUM3PrinterOutputController(self)
|
||||
super()._createMonitorViewFromQML()
|
||||
|
||||
def _onAuthenticationStateChanged(self):
|
||||
# We only accept commands if we are authenticated.
|
||||
|
|
|
@ -7,8 +7,8 @@ from UM.Version import Version
|
|||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
|
||||
|
||||
class LegacyUM3PrinterOutputController(PrinterOutputController):
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import json
|
||||
import os
|
||||
from queue import Queue
|
||||
from threading import Event, Thread
|
||||
from time import time
|
||||
import os
|
||||
from typing import Optional, TYPE_CHECKING, Dict
|
||||
|
||||
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
from cura.Settings.GlobalStack import GlobalStack # typing
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Signal import Signal, signalemitter
|
||||
|
@ -25,18 +27,25 @@ from UM.Version import Version
|
|||
|
||||
from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
|
||||
from .Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager
|
||||
from .Cloud.CloudOutputDevice import CloudOutputDevice # typing
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
from typing import Optional
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
|
||||
# Zero-Conf is used to detect printers, which are saved in a dict.
|
||||
# If we discover a printer that has the same key as the active machine instance a connection is made.
|
||||
@signalemitter
|
||||
class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||
addDeviceSignal = Signal()
|
||||
removeDeviceSignal = Signal()
|
||||
addDeviceSignal = Signal() # Called '...Signal' to avoid confusion with function-names.
|
||||
removeDeviceSignal = Signal() # Ditto ^^^.
|
||||
discoveredDevicesChanged = Signal()
|
||||
cloudFlowIsPossible = Signal()
|
||||
|
||||
|
@ -55,7 +64,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
self.addDeviceSignal.connect(self._onAddDevice)
|
||||
self.removeDeviceSignal.connect(self._onRemoveDevice)
|
||||
|
||||
self._application.globalContainerStackChanged.connect(self.reCheckConnections)
|
||||
self._application.globalContainerStackChanged.connect(self.refreshConnections)
|
||||
|
||||
self._discovered_devices = {}
|
||||
|
||||
|
@ -142,7 +151,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
self.addManualDevice(address)
|
||||
self.resetLastManualDevice()
|
||||
|
||||
def reCheckConnections(self):
|
||||
def refreshConnections(self):
|
||||
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not active_machine:
|
||||
return
|
||||
|
@ -173,9 +182,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
um_network_key = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key")
|
||||
if key == um_network_key:
|
||||
self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key])
|
||||
self.checkCloudFlowIsPossible()
|
||||
self.checkCloudFlowIsPossible(None)
|
||||
else:
|
||||
self.getOutputDeviceManager().removeOutputDevice(key)
|
||||
if key.startswith("manual:"):
|
||||
self.removeManualDeviceSignal.emit(self.getPluginId(), key, self._discovered_devices[key].address)
|
||||
|
||||
def stop(self):
|
||||
if self._zero_conf is not None:
|
||||
|
@ -183,6 +194,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
self._zero_conf.close()
|
||||
self._cloud_output_device_manager.stop()
|
||||
|
||||
def canAddManualDevice(self, address: str = "") -> ManualDeviceAdditionAttempt:
|
||||
# This plugin should always be the fallback option (at least try it):
|
||||
return ManualDeviceAdditionAttempt.POSSIBLE
|
||||
|
||||
def removeManualDevice(self, key, address = None):
|
||||
if key in self._discovered_devices:
|
||||
if not address:
|
||||
|
@ -194,6 +209,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
self._manual_instances.remove(address)
|
||||
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
|
||||
|
||||
self.removeManualDeviceSignal.emit(self.getPluginId(), key, address)
|
||||
|
||||
def addManualDevice(self, address):
|
||||
if address not in self._manual_instances:
|
||||
self._manual_instances.append(address)
|
||||
|
@ -215,7 +232,62 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
|
||||
self._checkManualDevice(address)
|
||||
|
||||
def _checkManualDevice(self, address):
|
||||
def _createMachineFromDiscoveredPrinter(self, key: str) -> None:
|
||||
discovered_device = self._discovered_devices.get(key)
|
||||
if discovered_device is None:
|
||||
Logger.log("e", "Could not find discovered device with key [%s]", key)
|
||||
return
|
||||
|
||||
group_name = discovered_device.getProperty("name")
|
||||
machine_type_id = discovered_device.getProperty("printer_type")
|
||||
|
||||
Logger.log("i", "Creating machine from network device with key = [%s], group name = [%s], printer type = [%s]",
|
||||
key, group_name, machine_type_id)
|
||||
|
||||
self._application.getMachineManager().addMachine(machine_type_id, group_name)
|
||||
# connect the new machine to that network printer
|
||||
self.associateActiveMachineWithPrinterDevice(discovered_device)
|
||||
# ensure that the connection states are refreshed.
|
||||
self.refreshConnections()
|
||||
|
||||
def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None:
|
||||
if not printer_device:
|
||||
return
|
||||
|
||||
Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key)
|
||||
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
return
|
||||
|
||||
meta_data = global_container_stack.getMetaData()
|
||||
|
||||
if "um_network_key" in meta_data: # Global stack already had a connection, but it's changed.
|
||||
old_network_key = meta_data["um_network_key"]
|
||||
# Since we might have a bunch of hidden stacks, we also need to change it there.
|
||||
metadata_filter = {"um_network_key": old_network_key}
|
||||
containers = self._application.getContainerRegistry().findContainerStacks(type = "machine", **metadata_filter)
|
||||
|
||||
for container in containers:
|
||||
container.setMetaDataEntry("um_network_key", printer_device.key)
|
||||
|
||||
# Delete old authentication data.
|
||||
Logger.log("d", "Removing old authentication id %s for device %s",
|
||||
global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key)
|
||||
|
||||
container.removeMetaDataEntry("network_authentication_id")
|
||||
container.removeMetaDataEntry("network_authentication_key")
|
||||
|
||||
# Ensure that these containers do know that they are configured for network connection
|
||||
container.addConfiguredConnectionType(printer_device.connectionType.value)
|
||||
|
||||
else: # Global stack didn't have a connection yet, configure it.
|
||||
global_container_stack.setMetaDataEntry("um_network_key", printer_device.key)
|
||||
global_container_stack.addConfiguredConnectionType(printer_device.connectionType.value)
|
||||
|
||||
self.refreshConnections()
|
||||
|
||||
def _checkManualDevice(self, address: str) -> None:
|
||||
# Check if a UM3 family device exists at this address.
|
||||
# If a printer responds, it will replace the preliminary printer created above
|
||||
# origin=manual is for tracking back the origin of the call
|
||||
|
@ -223,21 +295,30 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
name_request = QNetworkRequest(url)
|
||||
self._network_manager.get(name_request)
|
||||
|
||||
def _onNetworkRequestFinished(self, reply):
|
||||
def _onNetworkRequestFinished(self, reply: "QNetworkReply") -> None:
|
||||
reply_url = reply.url().toString()
|
||||
|
||||
if "system" in reply_url:
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
# Something went wrong with checking the firmware version!
|
||||
return
|
||||
address = reply.url().host()
|
||||
device = None
|
||||
properties = {} # type: Dict[bytes, bytes]
|
||||
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
# Either:
|
||||
# - Something went wrong with checking the firmware version!
|
||||
# - Something went wrong with checking the amount of printers the cluster has!
|
||||
# - Couldn't find printer at the address when trying to add it manually.
|
||||
if address in self._manual_instances:
|
||||
key = "manual:" + address
|
||||
self.removeManualDevice(key, address)
|
||||
return
|
||||
|
||||
if "system" in reply_url:
|
||||
try:
|
||||
system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
except:
|
||||
Logger.log("e", "Something went wrong converting the JSON.")
|
||||
return
|
||||
|
||||
address = reply.url().host()
|
||||
has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version
|
||||
instance_name = "manual:%s" % address
|
||||
properties = {
|
||||
|
@ -265,27 +346,27 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
self._network_manager.get(cluster_request)
|
||||
|
||||
elif "printers" in reply_url:
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
# Something went wrong with checking the amount of printers the cluster has!
|
||||
return
|
||||
# So we confirmed that the device is in fact a cluster printer, and we should now know how big it is.
|
||||
try:
|
||||
cluster_printers_list = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
except:
|
||||
Logger.log("e", "Something went wrong converting the JSON.")
|
||||
return
|
||||
address = reply.url().host()
|
||||
instance_name = "manual:%s" % address
|
||||
if instance_name in self._discovered_devices:
|
||||
device = self._discovered_devices[instance_name]
|
||||
properties = device.getProperties().copy()
|
||||
if b"incomplete" in properties:
|
||||
del properties[b"incomplete"]
|
||||
properties[b"cluster_size"] = len(cluster_printers_list)
|
||||
properties[b"cluster_size"] = str(len(cluster_printers_list)).encode("utf-8")
|
||||
self._onRemoveDevice(instance_name)
|
||||
self._onAddDevice(instance_name, address, properties)
|
||||
|
||||
def _onRemoveDevice(self, device_id):
|
||||
if device and address in self._manual_instances:
|
||||
self.getOutputDeviceManager().addOutputDevice(device)
|
||||
self.addManualDeviceSignal.emit(self.getPluginId(), device.getId(), address, properties)
|
||||
|
||||
def _onRemoveDevice(self, device_id: str) -> None:
|
||||
device = self._discovered_devices.pop(device_id, None)
|
||||
if device:
|
||||
if device.isConnected():
|
||||
|
@ -295,7 +376,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
except TypeError:
|
||||
# Disconnect already happened.
|
||||
pass
|
||||
|
||||
self._application.getDiscoveredPrintersModel().removeDiscoveredPrinter(device.address)
|
||||
self.discoveredDevicesChanged.emit()
|
||||
|
||||
def _onAddDevice(self, name, address, properties):
|
||||
|
@ -320,7 +401,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
|
||||
else:
|
||||
device = LegacyUM3OutputDevice.LegacyUM3OutputDevice(name, address, properties)
|
||||
|
||||
self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(address, device.getId(), name, self._createMachineFromDiscoveredPrinter, properties[b"printer_type"].decode("utf-8"), device)
|
||||
self._discovered_devices[device.getId()] = device
|
||||
self.discoveredDevicesChanged.emit()
|
||||
|
||||
|
@ -408,13 +489,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
return True
|
||||
|
||||
## Check if the prerequsites are in place to start the cloud flow
|
||||
def checkCloudFlowIsPossible(self) -> None:
|
||||
def checkCloudFlowIsPossible(self, cluster: Optional[CloudOutputDevice]) -> None:
|
||||
Logger.log("d", "Checking if cloud connection is possible...")
|
||||
|
||||
# Pre-Check: Skip if active machine already has been cloud connected or you said don't ask again
|
||||
active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
|
||||
active_machine = self._application.getMachineManager().activeMachine # type: Optional[GlobalStack]
|
||||
if active_machine:
|
||||
|
||||
# Check 1A: Printer isn't already configured for cloud
|
||||
if ConnectionType.CloudConnection.value in active_machine.configuredConnectionTypes:
|
||||
Logger.log("d", "Active machine was already configured for cloud.")
|
||||
|
@ -458,7 +538,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
if self._start_cloud_flow_message and not self._start_cloud_flow_message.visible:
|
||||
self._start_cloud_flow_message.show()
|
||||
|
||||
def _onCloudPrintingConfigured(self) -> None:
|
||||
def _onCloudPrintingConfigured(self, device) -> None:
|
||||
# Hide the cloud flow start message if it was hanging around already
|
||||
# For example: if the user already had the browser openen and made the association themselves
|
||||
if self._start_cloud_flow_message and self._start_cloud_flow_message.visible:
|
||||
|
@ -473,11 +553,20 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
# Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers
|
||||
active_machine = self._application.getMachineManager().activeMachine
|
||||
if active_machine:
|
||||
active_machine.setMetaDataEntry("do_not_show_cloud_message", True)
|
||||
|
||||
# The active machine _might_ not be the machine that was in the added cloud cluster and
|
||||
# then this will hide the cloud message for the wrong machine. So we only set it if the
|
||||
# host names match between the active machine and the newly added cluster
|
||||
saved_host_name = active_machine.getMetaDataEntry("um_network_key", "").split('.')[0]
|
||||
added_host_name = device.toDict()["host_name"]
|
||||
|
||||
if added_host_name == saved_host_name:
|
||||
active_machine.setMetaDataEntry("do_not_show_cloud_message", True)
|
||||
|
||||
return
|
||||
|
||||
def _onDontAskMeAgain(self, checked: bool) -> None:
|
||||
active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
|
||||
active_machine = self._application.getMachineManager().activeMachine # type: Optional[GlobalStack]
|
||||
if active_machine:
|
||||
active_machine.setMetaDataEntry("do_not_show_cloud_message", checked)
|
||||
if checked:
|
||||
|
@ -507,7 +596,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
self._cloud_flow_complete_message.hide()
|
||||
|
||||
# Check for cloud flow again with newly selected machine
|
||||
self.checkCloudFlowIsPossible()
|
||||
self.checkCloudFlowIsPossible(None)
|
||||
|
||||
def _createCloudFlowStartMessage(self):
|
||||
self._start_cloud_flow_message = Message(
|
||||
|
@ -517,7 +606,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"),
|
||||
"resources", "svg", "cloud-flow-start.svg"
|
||||
)),
|
||||
image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"),
|
||||
image_caption = i18n_catalog.i18nc("@info:status Ultimaker Cloud is a brand name and shouldn't be translated.", "Connect to Ultimaker Cloud"),
|
||||
option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."),
|
||||
option_state = False
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@ from typing import List
|
|||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
||||
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from .ConfigurationChangeModel import ConfigurationChangeModel
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from unittest.mock import patch, MagicMock
|
|||
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
from ...src.Cloud import CloudApiClient
|
||||
from ...src.Cloud.CloudOutputDevice import CloudOutputDevice
|
||||
from ...src.Cloud.Models.CloudClusterResponse import CloudClusterResponse
|
||||
|
@ -22,6 +22,7 @@ class TestCloudOutputDevice(TestCase):
|
|||
HOST_NAME = "ultimakersystem-ccbdd30044ec"
|
||||
HOST_GUID = "e90ae0ac-1257-4403-91ee-a44c9b7e8050"
|
||||
HOST_VERSION = "5.2.0"
|
||||
FRIENDLY_NAME = "My Friendly Printer"
|
||||
|
||||
STATUS_URL = "{}/connect/v1/clusters/{}/status".format(CuraCloudAPIRoot, CLUSTER_ID)
|
||||
PRINT_URL = "{}/connect/v1/clusters/{}/print/{}".format(CuraCloudAPIRoot, CLUSTER_ID, JOB_ID)
|
||||
|
@ -37,7 +38,8 @@ class TestCloudOutputDevice(TestCase):
|
|||
patched_method.start()
|
||||
|
||||
self.cluster = CloudClusterResponse(self.CLUSTER_ID, self.HOST_GUID, self.HOST_NAME, is_online=True,
|
||||
status="active", host_version=self.HOST_VERSION)
|
||||
status="active", host_version=self.HOST_VERSION,
|
||||
friendly_name=self.FRIENDLY_NAME)
|
||||
|
||||
self.network = NetworkManagerMock()
|
||||
self.account = MagicMock(isLoggedIn=True, accessToken="TestAccessToken")
|
||||
|
@ -60,7 +62,7 @@ class TestCloudOutputDevice(TestCase):
|
|||
# We test for these in order to make sure the correct file type is selected depending on the firmware version.
|
||||
def test_properties(self):
|
||||
self.assertEqual(self.device.firmwareVersion, self.HOST_VERSION)
|
||||
self.assertEqual(self.device.name, self.HOST_NAME)
|
||||
self.assertEqual(self.device.name, self.FRIENDLY_NAME)
|
||||
|
||||
def test_status(self):
|
||||
self.device._update()
|
||||
|
|
|
@ -7,6 +7,7 @@ from UM.OutputDevice.OutputDeviceManager import OutputDeviceManager
|
|||
from cura.UltimakerCloudAuthentication import CuraCloudAPIRoot
|
||||
from ...src.Cloud import CloudApiClient
|
||||
from ...src.Cloud import CloudOutputDeviceManager
|
||||
from ...src.Cloud.Models.CloudClusterResponse import CloudClusterResponse
|
||||
from .Fixtures import parseFixture, readFixture
|
||||
from .NetworkManagerMock import NetworkManagerMock, FakeSignal
|
||||
|
||||
|
@ -55,7 +56,9 @@ class TestCloudOutputDeviceManager(TestCase):
|
|||
devices = self.device_manager.getOutputDevices()
|
||||
# TODO: Check active device
|
||||
|
||||
response_clusters = self.clusters_response.get("data", [])
|
||||
response_clusters = []
|
||||
for cluster in self.clusters_response.get("data", []):
|
||||
response_clusters.append(CloudClusterResponse(**cluster).toDict())
|
||||
manager_clusters = sorted([device.clusterData.toDict() for device in self.manager._remote_clusters.values()],
|
||||
key=lambda cluster: cluster['cluster_id'], reverse=True)
|
||||
self.assertEqual(response_clusters, manager_clusters)
|
||||
|
@ -97,7 +100,7 @@ class TestCloudOutputDeviceManager(TestCase):
|
|||
|
||||
self.assertTrue(self.device_manager.getOutputDevice(cluster1["cluster_id"]).isConnected())
|
||||
self.assertIsNone(self.device_manager.getOutputDevice(cluster2["cluster_id"]))
|
||||
self.assertEquals([], active_machine_mock.setMetaDataEntry.mock_calls)
|
||||
self.assertEqual([], active_machine_mock.setMetaDataEntry.mock_calls)
|
||||
|
||||
def test_device_connects_by_network_key(self):
|
||||
active_machine_mock = self.app.getGlobalContainerStack.return_value
|
||||
|
|
|
@ -208,7 +208,7 @@ class TestSendMaterialJob(TestCase):
|
|||
|
||||
self.assertEqual(1, device_mock.createFormPart.call_count)
|
||||
self.assertEqual(1, device_mock.postFormWithParts.call_count)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
[call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"),
|
||||
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
|
||||
device_mock.method_calls)
|
||||
|
@ -238,7 +238,7 @@ class TestSendMaterialJob(TestCase):
|
|||
|
||||
self.assertEqual(1, device_mock.createFormPart.call_count)
|
||||
self.assertEqual(1, device_mock.postFormWithParts.call_count)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
[call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"),
|
||||
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
|
||||
device_mock.method_calls)
|
||||
|
|
|
@ -13,7 +13,7 @@ from time import sleep
|
|||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
|
||||
class AvrFirmwareUpdater(FirmwareUpdater):
|
||||
|
|
|
@ -10,9 +10,9 @@ from UM.PluginRegistry import PluginRegistry #To get the g-code output.
|
|||
from UM.Qt.Duration import DurationFormat
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
|
||||
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.GenericOutputController import GenericOutputController
|
||||
|
||||
from .AutoDetectBaudJob import AutoDetectBaudJob
|
||||
|
@ -226,6 +226,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
except SerialTimeoutException:
|
||||
Logger.log("w", "Timeout when sending command to printer via USB.")
|
||||
self._command_received.set()
|
||||
except SerialException:
|
||||
Logger.logException("w", "An unexpected exception occurred while writing to the serial.")
|
||||
self.setConnectionState(ConnectionState.Error)
|
||||
|
||||
def _update(self):
|
||||
while self._connection_state == ConnectionState.Connected and self._serial is not None:
|
||||
|
@ -371,10 +374,17 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum))
|
||||
|
||||
progress = (self._gcode_position / len(self._gcode))
|
||||
print_job = self._printers[0].activePrintJob
|
||||
try:
|
||||
progress = self._gcode_position / len(self._gcode)
|
||||
except ZeroDivisionError:
|
||||
# There is nothing to send!
|
||||
if print_job is not None:
|
||||
print_job.updateState("error")
|
||||
return
|
||||
|
||||
elapsed_time = int(time() - self._print_start_time)
|
||||
print_job = self._printers[0].activePrintJob
|
||||
|
||||
if print_job is None:
|
||||
controller = GenericOutputController(self)
|
||||
controller.setCanUpdateFirmware(True)
|
||||
|
|
|
@ -5,15 +5,13 @@ import threading
|
|||
import time
|
||||
import serial.tools.list_ports
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.PrinterOutputDevice import ConnectionState
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionState
|
||||
|
||||
from . import USBPrinterOutputDevice
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from typing import List
|
||||
|
||||
from cura.MachineAction import MachineAction
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Item
|
||||
{
|
||||
id: bedLevelMachineAction
|
||||
anchors.fill: parent;
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height * 3
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width * 3 / 4
|
||||
|
||||
Label
|
||||
{
|
||||
|
@ -26,17 +29,24 @@ Cura.MachineAction
|
|||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Build Plate Leveling")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18;
|
||||
font: UM.Theme.getFont("medium")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: pageTitle.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height * 3
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "To make sure your prints will come out great, you can now adjust your buildplate. When you click 'Move to Next Position' the nozzle will move to the different positions that can be adjusted.")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: bedlevelingText
|
||||
|
@ -45,37 +55,40 @@ Cura.MachineAction
|
|||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "For every position; insert a piece of paper under the nozzle and adjust the print build plate height. The print build plate height is right when the paper is slightly gripped by the tip of the nozzle.")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: bedlevelingWrapper
|
||||
anchors.top: bedlevelingText.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height * 3
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: childrenRect.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Button
|
||||
Cura.ActionButton
|
||||
{
|
||||
id: startBedLevelingButton
|
||||
text: catalog.i18nc("@action:button","Start Build Plate Leveling")
|
||||
text: catalog.i18nc("@action:button", "Start Build Plate Leveling")
|
||||
onClicked:
|
||||
{
|
||||
startBedLevelingButton.visible = false;
|
||||
bedlevelingButton.visible = true;
|
||||
manager.startBedLeveling();
|
||||
startBedLevelingButton.visible = false
|
||||
bedlevelingButton.visible = true
|
||||
manager.startBedLeveling()
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
Cura.ActionButton
|
||||
{
|
||||
id: bedlevelingButton
|
||||
text: catalog.i18nc("@action:button","Move to Next Position")
|
||||
text: catalog.i18nc("@action:button", "Move to Next Position")
|
||||
visible: false
|
||||
onClicked:
|
||||
{
|
||||
manager.moveToNextLevelPosition();
|
||||
manager.moveToNextLevelPosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.MachineAction import MachineAction
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Application import Application
|
||||
from UM.Util import parseBool
|
||||
|
||||
from cura.MachineAction import MachineAction
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
|
|
|
@ -1,49 +1,46 @@
|
|||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
anchors.fill: parent
|
||||
|
||||
Item
|
||||
{
|
||||
id: upgradeSelectionMachineAction
|
||||
anchors.fill: parent
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageTitle
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Select Printer Upgrades")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18;
|
||||
}
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").width * 5
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width * 4
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: pageTitle.bottom
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker 2.");
|
||||
text: catalog.i18nc("@label", "Please select any upgrades made to this Ultimaker 2.")
|
||||
font: UM.Theme.getFont("medium")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
CheckBox
|
||||
Cura.CheckBox
|
||||
{
|
||||
id: olssonBlockCheckBox
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
height: UM.Theme.getSize("setting_control").height
|
||||
|
||||
text: catalog.i18nc("@label", "Olsson Block")
|
||||
checked: manager.hasVariants
|
||||
onClicked: manager.hasVariants = checked
|
||||
|
@ -54,7 +51,5 @@ Cura.MachineAction
|
|||
onHasVariantsChanged: olssonBlockCheckBox.checked = manager.hasVariants
|
||||
}
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
from cura.MachineAction import MachineAction
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from UM.Application import Application
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## Action to check up if the self-built UMO was done correctly.
|
||||
class UMOCheckupMachineAction(MachineAction):
|
||||
def __init__(self):
|
||||
super().__init__("UMOCheckup", catalog.i18nc("@action", "Checkup"))
|
||||
self._qml_url = "UMOCheckupMachineAction.qml"
|
||||
self._hotend_target_temp = 180
|
||||
self._bed_target_temp = 60
|
||||
self._output_device = None
|
||||
self._bed_test_completed = False
|
||||
self._hotend_test_completed = False
|
||||
|
||||
# Endstop tests
|
||||
self._x_min_endstop_test_completed = False
|
||||
self._y_min_endstop_test_completed = False
|
||||
self._z_min_endstop_test_completed = False
|
||||
|
||||
self._check_started = False
|
||||
|
||||
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||
|
||||
onBedTestCompleted = pyqtSignal()
|
||||
onHotendTestCompleted = pyqtSignal()
|
||||
|
||||
onXMinEndstopTestCompleted = pyqtSignal()
|
||||
onYMinEndstopTestCompleted = pyqtSignal()
|
||||
onZMinEndstopTestCompleted = pyqtSignal()
|
||||
|
||||
bedTemperatureChanged = pyqtSignal()
|
||||
hotendTemperatureChanged = pyqtSignal()
|
||||
|
||||
def _onOutputDevicesChanged(self):
|
||||
# Check if this action was started, but no output device was found the first time.
|
||||
# If so, re-try now that an output device has been added/removed.
|
||||
if self._output_device is None and self._check_started:
|
||||
self.startCheck()
|
||||
|
||||
def _getPrinterOutputDevices(self):
|
||||
return [printer_output_device for printer_output_device in
|
||||
Application.getInstance().getOutputDeviceManager().getOutputDevices() if
|
||||
isinstance(printer_output_device, PrinterOutputDevice)]
|
||||
|
||||
def _reset(self):
|
||||
if self._output_device:
|
||||
self._output_device.bedTemperatureChanged.disconnect(self.bedTemperatureChanged)
|
||||
self._output_device.hotendTemperaturesChanged.disconnect(self.hotendTemperatureChanged)
|
||||
self._output_device.bedTemperatureChanged.disconnect(self._onBedTemperatureChanged)
|
||||
self._output_device.hotendTemperaturesChanged.disconnect(self._onHotendTemperatureChanged)
|
||||
self._output_device.endstopStateChanged.disconnect(self._onEndstopStateChanged)
|
||||
try:
|
||||
self._output_device.stopPollEndstop()
|
||||
except AttributeError as e: # Connection is probably not a USB connection. Something went pretty wrong if this happens.
|
||||
Logger.log("e", "An exception occurred while stopping end stop polling: %s" % str(e))
|
||||
|
||||
self._output_device = None
|
||||
|
||||
self._check_started = False
|
||||
self.checkStartedChanged.emit()
|
||||
|
||||
# Ensure everything is reset (and right signals are emitted again)
|
||||
self._bed_test_completed = False
|
||||
self.onBedTestCompleted.emit()
|
||||
self._hotend_test_completed = False
|
||||
self.onHotendTestCompleted.emit()
|
||||
|
||||
self._x_min_endstop_test_completed = False
|
||||
self.onXMinEndstopTestCompleted.emit()
|
||||
self._y_min_endstop_test_completed = False
|
||||
self.onYMinEndstopTestCompleted.emit()
|
||||
self._z_min_endstop_test_completed = False
|
||||
self.onZMinEndstopTestCompleted.emit()
|
||||
|
||||
self.heatedBedChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = onBedTestCompleted)
|
||||
def bedTestCompleted(self):
|
||||
return self._bed_test_completed
|
||||
|
||||
@pyqtProperty(bool, notify = onHotendTestCompleted)
|
||||
def hotendTestCompleted(self):
|
||||
return self._hotend_test_completed
|
||||
|
||||
@pyqtProperty(bool, notify = onXMinEndstopTestCompleted)
|
||||
def xMinEndstopTestCompleted(self):
|
||||
return self._x_min_endstop_test_completed
|
||||
|
||||
@pyqtProperty(bool, notify=onYMinEndstopTestCompleted)
|
||||
def yMinEndstopTestCompleted(self):
|
||||
return self._y_min_endstop_test_completed
|
||||
|
||||
@pyqtProperty(bool, notify=onZMinEndstopTestCompleted)
|
||||
def zMinEndstopTestCompleted(self):
|
||||
return self._z_min_endstop_test_completed
|
||||
|
||||
@pyqtProperty(float, notify = bedTemperatureChanged)
|
||||
def bedTemperature(self):
|
||||
if not self._output_device:
|
||||
return 0
|
||||
return self._output_device.bedTemperature
|
||||
|
||||
@pyqtProperty(float, notify=hotendTemperatureChanged)
|
||||
def hotendTemperature(self):
|
||||
if not self._output_device:
|
||||
return 0
|
||||
return self._output_device.hotendTemperatures[0]
|
||||
|
||||
def _onHotendTemperatureChanged(self):
|
||||
if not self._output_device:
|
||||
return
|
||||
if not self._hotend_test_completed:
|
||||
if self._output_device.hotendTemperatures[0] + 10 > self._hotend_target_temp and self._output_device.hotendTemperatures[0] - 10 < self._hotend_target_temp:
|
||||
self._hotend_test_completed = True
|
||||
self.onHotendTestCompleted.emit()
|
||||
|
||||
def _onBedTemperatureChanged(self):
|
||||
if not self._output_device:
|
||||
return
|
||||
if not self._bed_test_completed:
|
||||
if self._output_device.bedTemperature + 5 > self._bed_target_temp and self._output_device.bedTemperature - 5 < self._bed_target_temp:
|
||||
self._bed_test_completed = True
|
||||
self.onBedTestCompleted.emit()
|
||||
|
||||
def _onEndstopStateChanged(self, switch_type, state):
|
||||
if state:
|
||||
if switch_type == "x_min":
|
||||
self._x_min_endstop_test_completed = True
|
||||
self.onXMinEndstopTestCompleted.emit()
|
||||
elif switch_type == "y_min":
|
||||
self._y_min_endstop_test_completed = True
|
||||
self.onYMinEndstopTestCompleted.emit()
|
||||
elif switch_type == "z_min":
|
||||
self._z_min_endstop_test_completed = True
|
||||
self.onZMinEndstopTestCompleted.emit()
|
||||
|
||||
checkStartedChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify = checkStartedChanged)
|
||||
def checkStarted(self):
|
||||
return self._check_started
|
||||
|
||||
@pyqtSlot()
|
||||
def startCheck(self):
|
||||
self._check_started = True
|
||||
self.checkStartedChanged.emit()
|
||||
output_devices = self._getPrinterOutputDevices()
|
||||
if output_devices:
|
||||
self._output_device = output_devices[0]
|
||||
try:
|
||||
self._output_device.sendCommand("M18") # Turn off all motors so the user can move the axes
|
||||
self._output_device.startPollEndstop()
|
||||
self._output_device.bedTemperatureChanged.connect(self.bedTemperatureChanged)
|
||||
self._output_device.hotendTemperaturesChanged.connect(self.hotendTemperatureChanged)
|
||||
self._output_device.bedTemperatureChanged.connect(self._onBedTemperatureChanged)
|
||||
self._output_device.hotendTemperaturesChanged.connect(self._onHotendTemperatureChanged)
|
||||
self._output_device.endstopStateChanged.connect(self._onEndstopStateChanged)
|
||||
except AttributeError as e: # Connection is probably not a USB connection. Something went pretty wrong if this happens.
|
||||
Logger.log("e", "An exception occurred while starting end stop polling: %s" % str(e))
|
||||
|
||||
@pyqtSlot()
|
||||
def cooldownHotend(self):
|
||||
if self._output_device is not None:
|
||||
self._output_device.setTargetHotendTemperature(0, 0)
|
||||
|
||||
@pyqtSlot()
|
||||
def cooldownBed(self):
|
||||
if self._output_device is not None:
|
||||
self._output_device.setTargetBedTemperature(0)
|
||||
|
||||
@pyqtSlot()
|
||||
def heatupHotend(self):
|
||||
if self._output_device is not None:
|
||||
self._output_device.setTargetHotendTemperature(0, self._hotend_target_temp)
|
||||
|
||||
@pyqtSlot()
|
||||
def heatupBed(self):
|
||||
if self._output_device is not None:
|
||||
self._output_device.setTargetBedTemperature(self._bed_target_temp)
|
||||
|
||||
heatedBedChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify = heatedBedChanged)
|
||||
def hasHeatedBed(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
return global_container_stack.getProperty("machine_heated_bed", "value")
|
|
@ -1,288 +0,0 @@
|
|||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
Item
|
||||
{
|
||||
id: checkupMachineAction
|
||||
anchors.fill: parent;
|
||||
property int leftRow: (checkupMachineAction.width * 0.40) | 0
|
||||
property int rightRow: (checkupMachineAction.width * 0.60) | 0
|
||||
property bool heatupHotendStarted: false
|
||||
property bool heatupBedStarted: false
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"}
|
||||
Label
|
||||
{
|
||||
id: pageTitle
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Check Printer")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18;
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: pageTitle.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional");
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: startStopButtons
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: childrenRect.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
Button
|
||||
{
|
||||
id: startCheckButton
|
||||
text: catalog.i18nc("@action:button","Start Printer Check");
|
||||
onClicked:
|
||||
{
|
||||
checkupMachineAction.heatupHotendStarted = false;
|
||||
checkupMachineAction.heatupBedStarted = false;
|
||||
manager.startCheck();
|
||||
startCheckButton.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: checkupContent
|
||||
anchors.top: startStopButtons.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
visible: manager.checkStarted
|
||||
width: parent.width
|
||||
height: 250
|
||||
//////////////////////////////////////////////////////////
|
||||
Label
|
||||
{
|
||||
id: connectionLabel
|
||||
width: checkupMachineAction.leftRow
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Connection: ")
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: connectionStatus
|
||||
width: checkupMachineAction.rightRow
|
||||
anchors.left: connectionLabel.right
|
||||
anchors.top: parent.top
|
||||
wrapMode: Text.WordWrap
|
||||
text: checkupMachineAction.printerConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
|
||||
}
|
||||
//////////////////////////////////////////////////////////
|
||||
Label
|
||||
{
|
||||
id: endstopXLabel
|
||||
width: checkupMachineAction.leftRow
|
||||
anchors.left: parent.left
|
||||
anchors.top: connectionLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop X: ")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: endstopXStatus
|
||||
width: checkupMachineAction.rightRow
|
||||
anchors.left: endstopXLabel.right
|
||||
anchors.top: connectionLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.xMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
//////////////////////////////////////////////////////////////
|
||||
Label
|
||||
{
|
||||
id: endstopYLabel
|
||||
width: checkupMachineAction.leftRow
|
||||
anchors.left: parent.left
|
||||
anchors.top: endstopXLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop Y: ")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: endstopYStatus
|
||||
width: checkupMachineAction.rightRow
|
||||
anchors.left: endstopYLabel.right
|
||||
anchors.top: endstopXLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.yMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
Label
|
||||
{
|
||||
id: endstopZLabel
|
||||
width: checkupMachineAction.leftRow
|
||||
anchors.left: parent.left
|
||||
anchors.top: endstopYLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop Z: ")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: endstopZStatus
|
||||
width: checkupMachineAction.rightRow
|
||||
anchors.left: endstopZLabel.right
|
||||
anchors.top: endstopYLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.zMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
////////////////////////////////////////////////////////////
|
||||
Label
|
||||
{
|
||||
id: nozzleTempLabel
|
||||
width: checkupMachineAction.leftRow
|
||||
height: nozzleTempButton.height
|
||||
anchors.left: parent.left
|
||||
anchors.top: endstopZLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Nozzle temperature check: ")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: nozzleTempStatus
|
||||
width: (checkupMachineAction.rightRow * 0.4) | 0
|
||||
anchors.top: nozzleTempLabel.top
|
||||
anchors.left: nozzleTempLabel.right
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Item
|
||||
{
|
||||
id: nozzleTempButton
|
||||
width: (checkupMachineAction.rightRow * 0.3) | 0
|
||||
height: childrenRect.height
|
||||
anchors.top: nozzleTempLabel.top
|
||||
anchors.left: bedTempStatus.right
|
||||
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
|
||||
visible: checkupMachineAction.printerConnected
|
||||
Button
|
||||
{
|
||||
text: checkupMachineAction.heatupHotendStarted ? catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
|
||||
onClicked:
|
||||
{
|
||||
if (checkupMachineAction.heatupHotendStarted)
|
||||
{
|
||||
manager.cooldownHotend()
|
||||
checkupMachineAction.heatupHotendStarted = false
|
||||
} else
|
||||
{
|
||||
manager.heatupHotend()
|
||||
checkupMachineAction.heatupHotendStarted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: nozzleTemp
|
||||
anchors.top: nozzleTempLabel.top
|
||||
anchors.left: nozzleTempButton.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
width: (checkupMachineAction.rightRow * 0.2) | 0
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.hotendTemperature + "°C"
|
||||
font.bold: true
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
Label
|
||||
{
|
||||
id: bedTempLabel
|
||||
width: checkupMachineAction.leftRow
|
||||
height: bedTempButton.height
|
||||
anchors.left: parent.left
|
||||
anchors.top: nozzleTempLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Build plate temperature check:")
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: bedTempStatus
|
||||
width: (checkupMachineAction.rightRow * 0.4) | 0
|
||||
anchors.top: bedTempLabel.top
|
||||
anchors.left: bedTempLabel.right
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.bedTestCompleted ? catalog.i18nc("@info:status","Not checked"): catalog.i18nc("@info:status","Checked")
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
Item
|
||||
{
|
||||
id: bedTempButton
|
||||
width: (checkupMachineAction.rightRow * 0.3) | 0
|
||||
height: childrenRect.height
|
||||
anchors.top: bedTempLabel.top
|
||||
anchors.left: bedTempStatus.right
|
||||
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
Button
|
||||
{
|
||||
text: checkupMachineAction.heatupBedStarted ?catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
|
||||
onClicked:
|
||||
{
|
||||
if (checkupMachineAction.heatupBedStarted)
|
||||
{
|
||||
manager.cooldownBed()
|
||||
checkupMachineAction.heatupBedStarted = false
|
||||
} else
|
||||
{
|
||||
manager.heatupBed()
|
||||
checkupMachineAction.heatupBedStarted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: bedTemp
|
||||
width: (checkupMachineAction.rightRow * 0.2) | 0
|
||||
anchors.top: bedTempLabel.top
|
||||
anchors.left: bedTempButton.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.bedTemperature + "°C"
|
||||
font.bold: true
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: resultText
|
||||
visible: false
|
||||
anchors.top: bedTemp.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Everything is in order! You're done with your CheckUp.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +1,39 @@
|
|||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.1 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
anchors.fill: parent
|
||||
|
||||
Item
|
||||
{
|
||||
id: upgradeSelectionMachineAction
|
||||
anchors.fill: parent
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageTitle
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Select Printer Upgrades")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18;
|
||||
}
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").width * 5
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width * 4
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: pageTitle.bottom
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker Original");
|
||||
text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker Original")
|
||||
font: UM.Theme.getFont("medium")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
CheckBox
|
||||
Cura.CheckBox
|
||||
{
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
@ -46,7 +42,5 @@ Cura.MachineAction
|
|||
checked: manager.hasHeatedBed
|
||||
onClicked: manager.setHeatedBed(checked)
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
||||
class UserAgreement(QObject, Extension):
|
||||
def __init__(self, application):
|
||||
super(UserAgreement, self).__init__()
|
||||
self._application = application
|
||||
self._user_agreement_window = None
|
||||
self._user_agreement_context = None
|
||||
self._application.engineCreatedSignal.connect(self._onEngineCreated)
|
||||
|
||||
self._application.getPreferences().addPreference("general/accepted_user_agreement", False)
|
||||
|
||||
def _onEngineCreated(self):
|
||||
if not self._application.getPreferences().getValue("general/accepted_user_agreement"):
|
||||
self.showUserAgreement()
|
||||
|
||||
def showUserAgreement(self):
|
||||
if not self._user_agreement_window:
|
||||
self.createUserAgreementWindow()
|
||||
|
||||
self._user_agreement_window.show()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def didAgree(self, user_choice):
|
||||
if user_choice:
|
||||
Logger.log("i", "User agreed to the user agreement")
|
||||
self._application.getPreferences().setValue("general/accepted_user_agreement", True)
|
||||
self._user_agreement_window.hide()
|
||||
else:
|
||||
Logger.log("i", "User did NOT agree to the user agreement")
|
||||
self._application.getPreferences().setValue("general/accepted_user_agreement", False)
|
||||
self._application.quit()
|
||||
self._application.setNeedToShowUserAgreement(False)
|
||||
|
||||
def createUserAgreementWindow(self):
|
||||
path = os.path.join(self._application.getPluginRegistry().getPluginPath(self.getPluginId()), "UserAgreement.qml")
|
||||
self._user_agreement_window = self._application.createQmlComponent(path, {"manager": self})
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: baseDialog
|
||||
minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width * 0.75)
|
||||
minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height * 0.5)
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
title: catalog.i18nc("@title:window", "User Agreement")
|
||||
|
||||
TextArea
|
||||
{
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
anchors.bottom: buttonRow.top
|
||||
text: ' <center><h3>DISCLAIMER BY ULTIMAKER</h3></center>
|
||||
<p>PLEASE READ THIS DISCLAIMER CAREFULLY.</p>
|
||||
<p>EXCEPT WHEN OTHERWISE STATED IN WRITING, ULTIMAKER PROVIDES ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE “AS IS” WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF ULTIMAKER SOFTWARE IS WITH YOU.</p>
|
||||
<p>UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, IN NO EVENT WILL ULTIMAKER BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE.</p>
|
||||
'
|
||||
readOnly: true;
|
||||
textFormat: TextEdit.RichText
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: buttonRow
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
Button
|
||||
{
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@action:button", "I understand and agree")
|
||||
onClicked: {
|
||||
baseDialog.accepted()
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@action:button", "I don't agree")
|
||||
onClicked: {
|
||||
baseDialog.rejected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: manager.didAgree(true)
|
||||
onRejected: manager.didAgree(false)
|
||||
onClosing: manager.didAgree(false)
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import UserAgreement
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return {"extension": UserAgreement.UserAgreement(app)}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"name": "UserAgreement",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Ask the user once if he/she agrees with our license.",
|
||||
"api": "6.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -3,7 +3,7 @@ from typing import Tuple, List, Set, Dict
|
|||
import io
|
||||
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
from cura.PrinterOutputDevice import ConnectionType
|
||||
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
|
||||
|
||||
deleted_settings = {"bridge_wall_max_overhang"} # type: Set[str]
|
||||
renamed_configurations = {"connect_group_name": "group_name"} # type: Dict[str, str]
|
||||
|
|
|
@ -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 configparser
|
||||
|
@ -49,6 +49,15 @@ class VersionUpgrade40to41(VersionUpgrade):
|
|||
parser["general"]["version"] = "4"
|
||||
parser["metadata"]["setting_version"] = "7"
|
||||
|
||||
# Limit Maximum Deviation instead of Maximum Resolution. This should have approximately the same effect as before the algorithm change, only more consistent.
|
||||
if "meshfix_maximum_resolution" in parser["values"]:
|
||||
resolution = parser["values"]["meshfix_maximum_resolution"]
|
||||
if resolution.startswith("="):
|
||||
resolution = resolution[1:]
|
||||
deviation = "=(" + resolution + ") / 2"
|
||||
parser["values"]["meshfix_maximum_deviation"] = deviation
|
||||
del parser["values"]["meshfix_maximum_resolution"]
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
@ -62,6 +71,11 @@ class VersionUpgrade40to41(VersionUpgrade):
|
|||
parser["general"]["version"] = "6"
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
|
||||
# Remove changelog plugin
|
||||
if "latest_version_changelog_shown" in parser["general"]:
|
||||
del parser["general"]["latest_version_changelog_shown"]
|
||||
|
||||
parser["metadata"]["setting_version"] = "7"
|
||||
|
||||
result = io.StringIO()
|
||||
|
|
|
@ -10,21 +10,21 @@ from UM.Math.Color import Color
|
|||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Platform import Platform
|
||||
from UM.Event import Event
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.CuraView import CuraView
|
||||
from cura.Scene.ConvexHullNode import ConvexHullNode
|
||||
|
||||
from . import XRayPass
|
||||
|
||||
## View used to display a see-through version of objects with errors highlighted.
|
||||
class XRayView(View):
|
||||
class XRayView(CuraView):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
super().__init__(parent = None, use_empty_menu_placeholder = True)
|
||||
|
||||
self._xray_shader = None
|
||||
self._xray_pass = None
|
||||
|
|
|
@ -144,7 +144,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
|
||||
if ignored_metadata_keys is None:
|
||||
ignored_metadata_keys = set()
|
||||
ignored_metadata_keys |= {"setting_version", "definition", "status", "variant", "type", "base_file", "approximate_diameter", "id", "container_type", "name"}
|
||||
ignored_metadata_keys |= {"setting_version", "definition", "status", "variant", "type", "base_file", "approximate_diameter", "id", "container_type", "name", "compatible"}
|
||||
# remove the keys that we want to ignore in the metadata
|
||||
for key in ignored_metadata_keys:
|
||||
if key in metadata:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue