This commit is contained in:
Matt Jani 2020-01-28 15:28:26 +01:00
commit 155408d77d
25 changed files with 751 additions and 170 deletions

View file

@ -28,9 +28,10 @@ class CuraAPI(QObject):
# The main reason for this is that we want to prevent consumers of API to have a dependency on CuraApplication.
# Since the API is intended to be used by plugins, the cura application should have already created this.
def __new__(cls, application: Optional["CuraApplication"] = None):
if cls.__instance is None:
if cls.__instance is not None:
raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
if application is None:
raise Exception("Upon first time creation, the application must be set.")
raise RuntimeError("Upon first time creation, the application must be set.")
cls.__instance = super(CuraAPI, cls).__new__(cls)
cls._application = application
return cls.__instance

View file

@ -747,6 +747,11 @@ class MachineManager(QObject):
result = [] # type: List[str]
for setting_instance in container.findInstances():
setting_key = setting_instance.definition.key
if setting_key == "print_sequence":
old_value = container.getProperty(setting_key, "value")
Logger.log("d", "Reset setting [%s] in [%s] because its old value [%s] is no longer valid", setting_key, container, old_value)
result.append(setting_key)
continue
if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
continue

View file

@ -29,9 +29,13 @@ parser.add_argument("--debug",
known_args = vars(parser.parse_known_args()[0])
if with_sentry_sdk:
sentry_env = "production"
if ApplicationMetadata.CuraVersion == "master":
sentry_env = "unknown" # Start off with a "IDK"
if hasattr(sys, "frozen"):
sentry_env = "production" # A frozen build is a "real" distribution.
elif ApplicationMetadata.CuraVersion == "master":
sentry_env = "development"
elif "beta" in ApplicationMetadata.CuraVersion or "BETA" in ApplicationMetadata.CuraVersion:
sentry_env = "beta"
try:
if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
sentry_env = "nightly"

View file

@ -4,7 +4,8 @@
from configparser import ConfigParser
import zipfile
import os
from typing import cast, Dict, List, Optional, Tuple
import json
from typing import cast, Dict, List, Optional, Tuple, Any
import xml.etree.ElementTree as ET
@ -732,7 +733,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
base_file_name = os.path.basename(file_name)
self.setWorkspaceName(base_file_name)
return nodes
return nodes, self._loadMetadata(file_name)
@staticmethod
def _loadMetadata(file_name: str) -> Dict[str, Dict[str, Any]]:
archive = zipfile.ZipFile(file_name, "r")
metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")]
result = dict()
for metadata_file in metadata_files:
try:
plugin_id = metadata_file.split("/")[0]
result[plugin_id] = json.loads(archive.open("%s/plugin_metadata.json" % plugin_id).read().decode("utf-8"))
except Exception:
Logger.logException("w", "Unable to retrieve metadata for %s", metadata_file)
return result
def _processQualityChanges(self, global_stack):
if self._machine_info.quality_changes_info is None:

View file

@ -73,11 +73,25 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
version_config_parser.write(version_file_string)
archive.writestr(version_file, version_file_string.getvalue())
self._writePluginMetadataToArchive(archive)
# Close the archive & reset states.
archive.close()
mesh_writer.setStoreArchive(False)
return True
@staticmethod
def _writePluginMetadataToArchive(archive: zipfile.ZipFile) -> None:
file_name_template = "%s/plugin_metadata.json"
for plugin_id, metadata in Application.getInstance().getWorkspaceMetadataStorage().getAllData().items():
file_name = file_name_template % plugin_id
file_in_archive = zipfile.ZipInfo(file_name)
# We have to set the compress type of each file as well (it doesn't keep the type of the entire archive)
file_in_archive.compress_type = zipfile.ZIP_DEFLATED
import json
archive.writestr(file_in_archive, json.dumps(metadata, separators = (", ", ": "), indent = 4, skipkeys = True))
## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive.
# \param container That follows the \type{ContainerInterface} to archive.
# \param archive The archive to write to.

View file

@ -1,5 +1,6 @@
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from typing import Optional
from UM.Mesh.MeshWriter import MeshWriter
from UM.Math.Vector import Vector
@ -40,7 +41,7 @@ class ThreeMFWriter(MeshWriter):
}
self._unit_matrix_string = self._convertMatrixToString(Matrix())
self._archive = None
self._archive = None # type: Optional[zipfile.ZipFile]
self._store_archive = False
def _convertMatrixToString(self, matrix):

View file

@ -72,16 +72,23 @@ class DisplayFilenameAndLayerOnLCD(Script):
lcd_text = "M117 Printing " + name + " - Layer "
i = self.getSettingValueByKey("startNum")
for layer in data:
display_text = lcd_text + str(i) + " " + name
display_text = lcd_text + str(i)
layer_index = data.index(layer)
lines = layer.split("\n")
for line in lines:
if line.startswith(";LAYER_COUNT:"):
max_layer = line
max_layer = max_layer.split(":")[1]
if self.getSettingValueByKey("startNum") == 0:
max_layer = str(int(max_layer) - 1)
if line.startswith(";LAYER:"):
if self.getSettingValueByKey("maxlayer"):
display_text = display_text + " of " + max_layer
if not self.getSettingValueByKey("scroll"):
display_text = display_text + " " + name
else:
if not self.getSettingValueByKey("scroll"):
display_text = display_text + " " + name + "!"
else:
display_text = display_text + "!"
line_index = lines.index(line)

View file

@ -20,6 +20,8 @@ UM.Dialog{
maximumHeight: minimumHeight
margin: 0
property string actionButtonText: subscribedPackagesModel.hasIncompatiblePackages && !subscribedPackagesModel.hasCompatiblePackages ? catalog.i18nc("@button", "Dismiss") : catalog.i18nc("@button", "Next")
Rectangle
{
id: root
@ -125,26 +127,6 @@ UM.Dialog{
color: UM.Theme.getColor("text")
elide: Text.ElideRight
}
UM.TooltipArea
{
width: childrenRect.width;
height: childrenRect.height;
text: catalog.i18nc("@info:tooltip", "Dismisses the package and won't be shown in this dialog anymore")
anchors.right: parent.right
anchors.verticalCenter: packageIcon.verticalCenter
Label
{
text: "(Dismiss)"
font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text")
MouseArea
{
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: handler.dismissIncompatiblePackage(subscribedPackagesModel, model.package_id)
}
}
}
}
}
}
@ -158,7 +140,7 @@ UM.Dialog{
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.margins: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@button", "Next")
text: actionButtonText
onClicked: accept()
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width

View file

@ -5,6 +5,7 @@ import QtQuick 2.10
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
@ -20,31 +21,63 @@ UM.Dialog
minimumHeight: UM.Theme.getSize("license_window_minimum").height
width: minimumWidth
height: minimumHeight
backgroundColor: UM.Theme.getColor("main_background")
margin: screenScaleFactor * 10
Item
ColumnLayout
{
anchors.fill: parent
spacing: UM.Theme.getSize("thick_margin").height
UM.I18nCatalog{id: catalog; name: "cura"}
Label
{
id: licenseHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
text: licenseModel.headerText
Layout.fillWidth: true
text: catalog.i18nc("@label", "You need to accept the license to install the package")
wrapMode: Text.Wrap
renderType: Text.NativeRendering
}
Row {
id: packageRow
anchors.left: parent.left
anchors.right: parent.right
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
leftPadding: UM.Theme.getSize("narrow_margin").width
Image
{
id: icon
width: 30 * screenScaleFactor
height: width
fillMode: Image.PreserveAspectFit
source: licenseModel.iconUrl || "../../images/logobot.svg"
mipmap: true
}
Label
{
id: packageName
text: licenseModel.packageName
font.bold: true
anchors.verticalCenter: icon.verticalCenter
height: contentHeight
wrapMode: Text.Wrap
renderType: Text.NativeRendering
}
}
TextArea
{
id: licenseText
anchors.top: licenseHeader.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
Layout.fillWidth: true
Layout.fillHeight: true
anchors.topMargin: UM.Theme.getSize("default_margin").height
readOnly: true
text: licenseModel.licenseText
@ -57,7 +90,7 @@ UM.Dialog
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width
text: catalog.i18nc("@button", "Agree")
text: licenseModel.acceptButtonText
onClicked: { handler.onLicenseAccepted() }
}
]
@ -67,7 +100,7 @@ UM.Dialog
Cura.SecondaryButton
{
id: declineButton
text: catalog.i18nc("@button", "Decline and remove from account")
text: licenseModel.declineButtonText
onClicked: { handler.onLicenseDeclined() }
}
]

View file

@ -8,11 +8,12 @@ from UM import i18nCatalog
from UM.Logger import Logger
from UM.Message import Message
from UM.Signal import Signal
from cura.CuraApplication import CuraApplication
from cura.CuraApplication import CuraApplication, ApplicationMetadata
from ..CloudApiModel import CloudApiModel
from .SubscribedPackagesModel import SubscribedPackagesModel
from ..UltimakerCloudScope import UltimakerCloudScope
from typing import List, Dict, Any
class CloudPackageChecker(QObject):
def __init__(self, application: CuraApplication) -> None:
@ -25,12 +26,12 @@ class CloudPackageChecker(QObject):
self._application.initializationFinished.connect(self._onAppInitialized)
self._i18n_catalog = i18nCatalog("cura")
self._sdk_version = ApplicationMetadata.CuraSDKVersion
# This is a plugin, so most of the components required are not ready when
# this is initialized. Therefore, we wait until the application is ready.
def _onAppInitialized(self) -> None:
self._package_manager = self._application.getPackageManager()
# initial check
self._fetchUserSubscribedPackages()
# check again whenever the login state changes
@ -38,25 +39,51 @@ class CloudPackageChecker(QObject):
def _fetchUserSubscribedPackages(self) -> None:
if self._application.getCuraAPI().account.isLoggedIn:
self._getUserPackages()
self._getUserSubscribedPackages()
def _handleCompatibilityData(self, json_data) -> None:
user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
def _getUserSubscribedPackages(self) -> None:
Logger.debug("Requesting subscribed packages metadata from server.")
url = CloudApiModel.api_url_user_packages
self._application.getHttpRequestManager().get(url,
callback = self._onUserPackagesRequestFinished,
error_callback = self._onUserPackagesRequestFinished,
scope = self._scope)
def _onUserPackagesRequestFinished(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"] = None) -> None:
if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
Logger.log("w",
"Requesting user packages failed, response code %s while trying to connect to %s",
reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
return
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# Check for errors:
if "errors" in json_data:
for error in json_data["errors"]:
Logger.log("e", "%s", error["title"])
return
self._handleCompatibilityData(json_data["data"])
except json.decoder.JSONDecodeError:
Logger.log("w", "Received invalid JSON for user subscribed packages from the Web Marketplace")
def _handleCompatibilityData(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
user_subscribed_packages = [plugin["package_id"] for plugin in subscribed_packages_payload]
user_installed_packages = self._package_manager.getUserInstalledPackages()
# We need to re-evaluate the dismissed packages
# (i.e. some package might got updated to the correct SDK version in the meantime,
# hence remove them from the Dismissed Incompatible list)
self._package_manager.reEvaluateDismissedPackages(subscribed_packages_payload, self._sdk_version)
user_dismissed_packages = self._package_manager.getDismissedPackages()
if user_dismissed_packages:
user_installed_packages += user_dismissed_packages
# We check if there are packages installed in Cloud Marketplace but not in Cura marketplace
# We check if there are packages installed in Web Marketplace but not in Cura marketplace
package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
self._model.setMetadata(json_data)
self._model.addDiscrepancies(package_discrepancy)
self._model.initialize()
if not self._model.hasCompatiblePackages:
return None
if package_discrepancy:
self._model.addDiscrepancies(package_discrepancy)
self._model.initialize(subscribed_packages_payload)
self._handlePackageDiscrepancies()
def _handlePackageDiscrepancies(self) -> None:
@ -77,34 +104,3 @@ class CloudPackageChecker(QObject):
def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
sync_message.hide()
self.discrepancies.emit(self._model)
def _getUserPackages(self) -> None:
Logger.log("d", "Requesting subscribed packages metadata from server.")
url = CloudApiModel.api_url_user_packages
self._application.getHttpRequestManager().get(url,
callback = self._onUserPackagesRequestFinished,
error_callback = self._onUserPackagesRequestFinished,
scope = self._scope)
def _onUserPackagesRequestFinished(self,
reply: "QNetworkReply",
error: Optional["QNetworkReply.NetworkError"] = None) -> None:
if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
Logger.log("w",
"Requesting user packages failed, response code %s while trying to connect to %s",
reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
return
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# Check for errors:
if "errors" in json_data:
for error in json_data["errors"]:
Logger.log("e", "%s", error["title"])
return
self._handleCompatibilityData(json_data["data"])
except json.decoder.JSONDecodeError:
Logger.log("w", "Received invalid JSON for user packages")

View file

@ -28,13 +28,12 @@ class DiscrepanciesPresenter(QObject):
assert self._dialog
self._dialog.accepted.connect(lambda: self._onConfirmClicked(model))
@pyqtSlot("QVariant", str)
def dismissIncompatiblePackage(self, model: SubscribedPackagesModel, package_id: str) -> None:
model.dismissPackage(package_id) # update the model to update the view
self._package_manager.dismissPackage(package_id) # adds this package_id as dismissed in the user config file
def _onConfirmClicked(self, model: SubscribedPackagesModel) -> None:
# If there are incompatible packages - automatically dismiss them
if model.getIncompatiblePackages():
self._package_manager.dismissAllIncompatiblePackages(model.getIncompatiblePackages())
# For now, all compatible packages presented to the user should be installed.
# Later, we might remove items for which the user unselected the package
if model.getCompatiblePackages():
model.setItems(model.getCompatiblePackages())
self.packageMutations.emit(model)

View file

@ -62,7 +62,8 @@ class DownloadPresenter:
"received": 0,
"total": 1, # make sure this is not considered done yet. Also divByZero-safe
"file_written": None,
"request_data": request_data
"request_data": request_data,
"package_model": item
}
self._started = True
@ -128,7 +129,14 @@ class DownloadPresenter:
if not item["file_written"]:
return False
success_items = {package_id : value["file_written"] for package_id, value in self._progress.items()}
success_items = {
package_id:
{
"package_path": value["file_written"],
"icon_url": value["package_model"]["icon_url"]
}
for package_id, value in self._progress.items()
}
error_items = [package_id for package_id in self._error]
self._progress_message.hide()

View file

@ -6,31 +6,52 @@ catalog = i18nCatalog("cura")
# Model for the ToolboxLicenseDialog
class LicenseModel(QObject):
dialogTitleChanged = pyqtSignal()
headerChanged = pyqtSignal()
licenseTextChanged = pyqtSignal()
DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline")
ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree")
def __init__(self) -> None:
dialogTitleChanged = pyqtSignal()
packageNameChanged = pyqtSignal()
licenseTextChanged = pyqtSignal()
iconChanged = pyqtSignal()
def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None:
super().__init__()
self._current_page_idx = 0
self._page_count = 1
self._dialogTitle = ""
self._header_text = ""
self._license_text = ""
self._package_name = ""
self._icon_url = ""
self._decline_button_text = decline_button_text
@pyqtProperty(str, constant = True)
def acceptButtonText(self):
return self.ACCEPT_BUTTON_TEXT
@pyqtProperty(str, constant = True)
def declineButtonText(self):
return self._decline_button_text
@pyqtProperty(str, notify=dialogTitleChanged)
def dialogTitle(self) -> str:
return self._dialogTitle
@pyqtProperty(str, notify=headerChanged)
def headerText(self) -> str:
return self._header_text
@pyqtProperty(str, notify=packageNameChanged)
def packageName(self) -> str:
return self._package_name
def setPackageName(self, name: str) -> None:
self._header_text = name + ": " + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
self.headerChanged.emit()
self._package_name = name
self.packageNameChanged.emit()
@pyqtProperty(str, notify=iconChanged)
def iconUrl(self) -> str:
return self._icon_url
def setIconUrl(self, url: str):
self._icon_url = url
self.iconChanged.emit()
@pyqtProperty(str, notify=licenseTextChanged)
def licenseText(self) -> str:

View file

@ -17,6 +17,7 @@ class LicensePresenter(QObject):
def __init__(self, app: CuraApplication) -> None:
super().__init__()
self._catalog = i18nCatalog("cura")
self._dialog = None # type: Optional[QObject]
self._package_manager = app.getPackageManager() # type: PackageManager
# Emits List[Dict[str, [Any]] containing for example
@ -25,7 +26,8 @@ class LicensePresenter(QObject):
self._current_package_idx = 0
self._package_models = [] # type: List[Dict]
self._license_model = LicenseModel() # type: LicenseModel
decline_button_text = self._catalog.i18nc("@button", "Decline and remove from account")
self._license_model = LicenseModel(decline_button_text=decline_button_text) # type: LicenseModel
self._app = app
@ -34,7 +36,7 @@ class LicensePresenter(QObject):
## Show a license dialog for multiple packages where users can read a license and accept or decline them
# \param plugin_path: Root directory of the Toolbox plugin
# \param packages: Dict[package id, file path]
def present(self, plugin_path: str, packages: Dict[str, str]) -> None:
def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None:
path = os.path.join(plugin_path, self._compatibility_dialog_path)
self._initState(packages)
@ -42,7 +44,7 @@ class LicensePresenter(QObject):
if self._dialog is None:
context_properties = {
"catalog": i18nCatalog("cura"),
"catalog": self._catalog,
"licenseModel": self._license_model,
"handler": self
}
@ -60,18 +62,20 @@ class LicensePresenter(QObject):
self._package_models[self._current_package_idx]["accepted"] = False
self._checkNextPage()
def _initState(self, packages: Dict[str, str]) -> None:
def _initState(self, packages: Dict[str, Dict[str, str]]) -> None:
self._package_models = [
{
"package_id" : package_id,
"package_path" : package_path,
"package_path" : item["package_path"],
"icon_url" : item["icon_url"],
"accepted" : None #: None: no answer yet
}
for package_id, package_path in packages.items()
for package_id, item in packages.items()
]
def _presentCurrentPackage(self) -> None:
package_model = self._package_models[self._current_package_idx]
package_info = self._package_manager.getPackageInfo(package_model["package_path"])
license_content = self._package_manager.getPackageLicense(package_model["package_path"])
if license_content is None:
# Implicitly accept when there is no license
@ -79,7 +83,8 @@ class LicensePresenter(QObject):
return
self._license_model.setCurrentPageIdx(self._current_package_idx)
self._license_model.setPackageName(package_model["package_id"])
self._license_model.setPackageName(package_info["display_name"])
self._license_model.setIconUrl(package_model["icon_url"])
self._license_model.setLicenseText(license_content)
if self._dialog:
self._dialog.open() # Does nothing if already open

View file

@ -37,27 +37,18 @@ class SubscribedPackagesModel(ListModel):
return True
return False
# Sets the "is_compatible" to True for the given package, in memory
@pyqtSlot()
def dismissPackage(self, package_id: str) -> None:
package = self.find(key="package_id", value=package_id)
if package != -1:
self.setProperty(package, property="is_dismissed", value=True)
Logger.debug("Package {} has been dismissed".format(package_id))
def setMetadata(self, data: List[Dict[str, List[Any]]]) -> None:
self._metadata = data
def addDiscrepancies(self, discrepancy: List[str]) -> None:
self._discrepancies = discrepancy
def getCompatiblePackages(self):
return [x for x in self._items if x["is_compatible"]]
def getCompatiblePackages(self) -> List[Dict[str, Any]]:
return [package for package in self._items if package["is_compatible"]]
def initialize(self) -> None:
def getIncompatiblePackages(self) -> List[str]:
return [package["package_id"] for package in self._items if not package["is_compatible"]]
def initialize(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
self._items.clear()
for item in self._metadata:
for item in subscribed_packages_payload:
if item["package_id"] not in self._discrepancies:
continue
package = {

View file

@ -63,9 +63,9 @@ class SyncOrchestrator(Extension):
self._download_presenter.download(mutations)
## Called when a set of packages have finished downloading
# \param success_items: Dict[package_id, file_path]
# \param success_items: Dict[package_id, Dict[str, str]]
# \param error_items: List[package_id]
def _onDownloadFinished(self, success_items: Dict[str, str], error_items: List[str]) -> None:
def _onDownloadFinished(self, success_items: Dict[str, Dict[str, str]], error_items: List[str]) -> None:
if error_items:
message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items)))
self._showErrorMessage(message)

View file

@ -154,10 +154,11 @@ class Toolbox(QObject, Extension):
def getLicenseDialogPluginFileLocation(self) -> str:
return self._license_dialog_plugin_file_location
def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str) -> None:
def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str, icon_url: str) -> None:
# Set page 1/1 when opening the dialog for a single package
self._license_model.setCurrentPageIdx(0)
self._license_model.setPageCount(1)
self._license_model.setIconUrl(icon_url)
self._license_model.setPackageName(plugin_name)
self._license_model.setLicenseText(license_content)
@ -670,14 +671,17 @@ class Toolbox(QObject, Extension):
return
license_content = self._package_manager.getPackageLicense(file_path)
package_id = package_info["package_id"]
if license_content is not None:
self.openLicenseDialog(package_info["package_id"], license_content, file_path)
# get the icon url for package_id, make sure the result is a string, never None
icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or ""
self.openLicenseDialog(package_info["display_name"], license_content, file_path, icon_url)
return
package_id = self.install(file_path)
if package_id != package_info["package_id"]:
Logger.error("Installed package {} does not match {}".format(package_id, package_info["package_id"]))
self.subscribe(package_id)
installed_id = self.install(file_path)
if installed_id != package_id:
Logger.error("Installed package {} does not match {}".format(installed_id, package_id))
self.subscribe(installed_id)
# Getter & Setters for Properties:
# --------------------------------------------------------------------------
@ -699,14 +703,14 @@ class Toolbox(QObject, Extension):
def isDownloading(self) -> bool:
return self._is_downloading
def setActivePackage(self, package: Dict[str, Any]) -> None:
def setActivePackage(self, package: QObject) -> None:
if self._active_package != package:
self._active_package = package
self.activePackageChanged.emit()
## The active package is the package that is currently being downloaded
@pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
def activePackage(self) -> Optional[Dict[str, Any]]:
def activePackage(self) -> Optional[QObject]:
return self._active_package
def setViewCategory(self, category: str = "plugin") -> None:

View file

@ -5984,7 +5984,7 @@
"print_sequence":
{
"label": "Print Sequence",
"description": "Whether to print all models one layer at a time or to wait for one model to finish, before moving on to the next. One at a time mode is only possible if all models are separated in such a way that the whole print head can move in between and all models are lower than the distance between the nozzle and the X/Y axes.",
"description": "Whether to print all models one layer at a time or to wait for one model to finish, before moving on to the next. One at a time mode is possible if a) only one extruder is enabled and b) all models are separated in such a way that the whole print head can move in between and all models are lower than the distance between the nozzle and the X/Y axes. ",
"type": "enum",
"options":
{

View file

@ -18,10 +18,10 @@
"default_value": "skirt"
},
"bottom_thickness": {
"value": "0.5"
"value": "0.6"
},
"brim_width": {
"value": "2.0"
"value": "3.0"
},
"cool_fan_enabled": {
"value": "True"
@ -39,19 +39,28 @@
"value": "True"
},
"cool_min_layer_time": {
"value": "5.0"
"value": "1.0"
},
"cool_min_speed": {
"value": "10.0"
"value": "5.0"
},
"infill_before_walls": {
"value": "True"
},
"infill_line_width": {
"value": "0.6"
},
"infill_overlap": {
"value": "15.0"
},
"infill_sparse_density": {
"value": "26.0"
},
"ironing_enabled": {
"value": "True"
},
"layer_0_z_overlap": {
"value": "0.22"
"value": "0.11"
},
"layer_height_0": {
"value": "0.3"
@ -60,11 +69,23 @@
"value": "100"
},
"machine_end_gcode": {
"default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 E-5 X-20 Y-20 ;retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG0 Z{machine_height} ;move the platform all the way down\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done"
"default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-4 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done"
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_head_with_fans_polygon":
{
"default_value": [
[-26, -27],
[38, -27],
[38, 55],
[-26, 55]
]
},
"gantry_height": {
"value": "8"
},
"machine_height": {
"value": "100"
},
@ -72,7 +93,7 @@
"default_value": "Renkforce RF100"
},
"machine_start_gcode": {
"default_value": ";Start GCode\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\n;Put printing message on LCD screen\nM117 Printing..."
"default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E6.0 ;extrude 6.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_width": {
"value": "100"
@ -90,7 +111,7 @@
"value": "True"
},
"raft_airgap": {
"value": "0.22"
"value": "0.33"
},
"raft_base_line_spacing": {
"value": "3.0"
@ -111,22 +132,25 @@
"value": "0.27"
},
"raft_margin": {
"value": "5.0"
"value": "6.0"
},
"raft_speed": {
"value": "20.0"
},
"raft_surface_layers": {
"value": "2.0"
"value": "2"
},
"raft_surface_line_spacing": {
"value": "3.0"
"value": "0.4"
},
"raft_surface_line_width": {
"value": "0.4"
},
"raft_surface_thickness": {
"value": "0.27"
"value": "0.1"
},
"retraction_amount": {
"value": "2.0"
"value": "5.0"
},
"retraction_combing": {
"default_value": "all"
@ -134,15 +158,12 @@
"retraction_enable": {
"value": "True"
},
"retraction_hop_enabled": {
"retraction_hop": {
"value": "1.0"
},
"retraction_min_travel": {
"value": "1.5"
},
"retraction_speed": {
"value": "40.0"
},
"skin_overlap": {
"value": "15.0"
},
@ -185,6 +206,9 @@
"support_infill_rate": {
"value": "15 if support_enable else 0 if support_tree_enable else 15"
},
"support_line_width": {
"value": "0.6"
},
"support_pattern": {
"default_value": "lines"
},
@ -192,13 +216,13 @@
"default_value": "everywhere"
},
"support_xy_distance": {
"value": "0.5"
"value": "0.7"
},
"support_z_distance": {
"value": "0.1"
"value": "0.35"
},
"top_thickness": {
"value": "0.5"
"top_bottom_thickness": {
"value": "0.8"
},
"wall_thickness": {
"value": "0.8"

View file

@ -0,0 +1,231 @@
{
"version": 2,
"name": "Renkforce RF100 V2",
"inherits": "fdmprinter",
"metadata": {
"author": "Simon Peter (based on RF100.ini by Conrad Electronic SE)",
"file_formats": "text/x-gcode",
"manufacturer": "Renkforce",
"visible": true,
"machine_extruder_trains":
{
"0": "renkforce_rf100_extruder_0"
}
},
"overrides": {
"adhesion_type": {
"default_value": "skirt"
},
"bottom_thickness": {
"value": "0.6"
},
"brim_width": {
"value": "3.0"
},
"cool_fan_enabled": {
"value": "True"
},
"cool_fan_full_at_height": {
"value": "0.5"
},
"cool_fan_speed_max": {
"value": "100.0"
},
"cool_fan_speed_min": {
"value": "100.0"
},
"cool_lift_head": {
"value": "True"
},
"cool_min_layer_time": {
"value": "1.0"
},
"cool_min_speed": {
"value": "5.0"
},
"infill_before_walls": {
"value": "True"
},
"infill_line_width": {
"value": "0.6"
},
"infill_overlap": {
"value": "15.0"
},
"infill_sparse_density": {
"value": "26.0"
},
"ironing_enabled": {
"value": "True"
},
"layer_0_z_overlap": {
"value": "0.11"
},
"layer_height_0": {
"value": "0.3"
},
"machine_depth": {
"value": "120"
},
"machine_end_gcode": {
"default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-4 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done"
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_head_with_fans_polygon":
{
"default_value": [
[-26, -27],
[38, -27],
[38, 55],
[-26, 55]
]
},
"gantry_height": {
"value": "8"
},
"machine_height": {
"value": "120"
},
"machine_name": {
"default_value": "Renkforce RF100 V2"
},
"machine_start_gcode": {
"default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E6.0 ;extrude 6.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_width": {
"value": "120"
},
"material_bed_temperature": {
"enabled": false
},
"material_flow": {
"value": "110"
},
"material_print_temperature": {
"value": "210.0"
},
"ooze_shield_enabled": {
"value": "True"
},
"raft_airgap": {
"value": "0.33"
},
"raft_base_line_spacing": {
"value": "3.0"
},
"raft_base_line_width": {
"value": "1.0"
},
"raft_base_thickness": {
"value": "0.3"
},
"raft_interface_line_spacing": {
"value": "3.0"
},
"raft_interface_line_width": {
"value": "0.4"
},
"raft_interface_thickness": {
"value": "0.27"
},
"raft_margin": {
"value": "6.0"
},
"raft_speed": {
"value": "20.0"
},
"raft_surface_layers": {
"value": "2"
},
"raft_surface_line_spacing": {
"value": "0.4"
},
"raft_surface_line_width": {
"value": "0.4"
},
"raft_surface_thickness": {
"value": "0.1"
},
"retraction_amount": {
"value": "5.0"
},
"retraction_combing": {
"default_value": "all"
},
"retraction_enable": {
"value": "True"
},
"retraction_hop": {
"value": "1.0"
},
"retraction_min_travel": {
"value": "1.5"
},
"skin_overlap": {
"value": "15.0"
},
"skirt_brim_minimal_length": {
"value": "150.0"
},
"skirt_gap": {
"value": "3.0"
},
"skirt_line_count": {
"value": "3"
},
"speed_infill": {
"value": "50.0"
},
"speed_layer_0": {
"value": "15.0"
},
"speed_print": {
"value": "50.0"
},
"speed_topbottom": {
"value": "30.0"
},
"speed_travel": {
"value": "50.0"
},
"speed_wall_0": {
"value": "25.0"
},
"speed_wall_x": {
"value": "35.0"
},
"support_angle": {
"value": "60.0"
},
"support_enable": {
"value": "False"
},
"support_infill_rate": {
"value": "15 if support_enable else 0 if support_tree_enable else 15"
},
"support_line_width": {
"value": "0.6"
},
"support_pattern": {
"default_value": "lines"
},
"support_type": {
"default_value": "everywhere"
},
"support_xy_distance": {
"value": "0.7"
},
"support_z_distance": {
"value": "0.35"
},
"top_bottom_thickness": {
"value": "0.8"
},
"wall_thickness": {
"value": "0.8"
}
}
}

View file

@ -0,0 +1,219 @@
{
"version": 2,
"name": "Renkforce RF100 XL",
"inherits": "fdmprinter",
"metadata": {
"author": "Simon Peter (based on RF100.ini by Conrad Electronic SE)",
"file_formats": "text/x-gcode",
"manufacturer": "Renkforce",
"visible": true,
"machine_extruder_trains":
{
"0": "renkforce_rf100_xl_extruder_0"
}
},
"overrides": {
"adhesion_type": {
"default_value": "skirt"
},
"bottom_thickness": {
"value": "0.6"
},
"brim_width": {
"value": "3.0"
},
"cool_fan_enabled": {
"value": "True"
},
"cool_fan_full_at_height": {
"value": "0.5"
},
"cool_fan_speed_max": {
"value": "100.0"
},
"cool_fan_speed_min": {
"value": "100.0"
},
"cool_lift_head": {
"value": "True"
},
"cool_min_layer_time": {
"value": "1.0"
},
"cool_min_speed": {
"value": "5.0"
},
"infill_before_walls": {
"value": "True"
},
"infill_line_width": {
"value": "0.6"
},
"infill_overlap": {
"value": "15.0"
},
"infill_sparse_density": {
"value": "26.0"
},
"ironing_enabled": {
"value": "True"
},
"layer_0_z_overlap": {
"value": "0.11"
},
"layer_height_0": {
"value": "0.3"
},
"machine_depth": {
"value": "200"
},
"machine_end_gcode": {
"default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-4 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done"
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_heated_bed": {
"default_value": "true"
},
"machine_height": {
"value": "200"
},
"machine_name": {
"default_value": "Renkforce RF100 XL"
},
"machine_start_gcode": {
"default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E6.0 ;extrude 6.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_width": {
"value": "200"
},
"material_bed_temperature": {
"value": "70"
},
"material_print_temperature": {
"value": "210.0"
},
"ooze_shield_enabled": {
"value": "True"
},
"raft_airgap": {
"value": "0.33"
},
"raft_base_line_spacing": {
"value": "3.0"
},
"raft_base_line_width": {
"value": "1.0"
},
"raft_base_thickness": {
"value": "0.3"
},
"raft_interface_line_spacing": {
"value": "3.0"
},
"raft_interface_line_width": {
"value": "0.4"
},
"raft_interface_thickness": {
"value": "0.27"
},
"raft_margin": {
"value": "6.0"
},
"raft_speed": {
"value": "20.0"
},
"raft_surface_layers": {
"value": "2"
},
"raft_surface_line_spacing": {
"value": "0.4"
},
"raft_surface_line_width": {
"value": "0.4"
},
"raft_surface_thickness": {
"value": "0.1"
},
"retraction_amount": {
"value": "5.0"
},
"retraction_combing": {
"default_value": "all"
},
"retraction_enable": {
"value": "True"
},
"retraction_hop": {
"value": "1.0"
},
"retraction_min_travel": {
"value": "1.5"
},
"skin_overlap": {
"value": "15.0"
},
"skirt_brim_minimal_length": {
"value": "150.0"
},
"skirt_gap": {
"value": "3.0"
},
"skirt_line_count": {
"value": "3"
},
"speed_infill": {
"value": "50.0"
},
"speed_layer_0": {
"value": "15.0"
},
"speed_print": {
"value": "50.0"
},
"speed_topbottom": {
"value": "30.0"
},
"speed_travel": {
"value": "50.0"
},
"speed_wall_0": {
"value": "25.0"
},
"speed_wall_x": {
"value": "35.0"
},
"support_angle": {
"value": "60.0"
},
"support_enable": {
"value": "False"
},
"support_infill_rate": {
"value": "15 if support_enable else 0 if support_tree_enable else 15"
},
"support_line_width": {
"value": "0.6"
},
"support_pattern": {
"default_value": "lines"
},
"support_type": {
"default_value": "everywhere"
},
"support_xy_distance": {
"value": "0.7"
},
"support_z_distance": {
"value": "0.35"
},
"top_bottom_thickness": {
"value": "0.8"
},
"wall_thickness": {
"value": "0.8"
}
}
}

View file

@ -0,0 +1,15 @@
{
"version": 2,
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata": {
"machine": "renkforce_rf100_xl",
"position": "0"
},
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 }
}
}

View file

@ -127,8 +127,8 @@ Item
icon: StandardIcon.Question
onYes:
{
CuraApplication.deleteAll();
Cura.Actions.resetProfile.trigger();
CuraApplication.resetWorkspace()
Cura.Actions.resetProfile.trigger()
UM.Controller.setActiveStage("PrepareStage")
}
}

View file

@ -125,6 +125,7 @@ Item
id: createMenuButton
text: catalog.i18nc("@action:button", "Create")
iconName: "list-add"
enabled: Cura.MachineManager.activeMachine.hasMaterials
onClicked:
{
forceActiveFocus();
@ -174,7 +175,7 @@ Item
forceActiveFocus();
importMaterialDialog.open();
}
visible: true
enabled: Cura.MachineManager.activeMachine.hasMaterials
}
// Export button

View file

@ -202,8 +202,9 @@ Item
// dragging a tool handle.
Rectangle
{
x: -base.x + base.mouseX + UM.Theme.getSize("default_margin").width
y: -base.y + base.mouseY + UM.Theme.getSize("default_margin").height
id: toolInfo
x: visible ? -base.x + base.mouseX + UM.Theme.getSize("default_margin").width: 0
y: visible ? -base.y + base.mouseY + UM.Theme.getSize("default_margin").height: 0
width: toolHint.width + UM.Theme.getSize("default_margin").width
height: toolHint.height;