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,11 +28,12 @@ class CuraAPI(QObject):
# The main reason for this is that we want to prevent consumers of API to have a dependency on CuraApplication. # 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. # 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): def __new__(cls, application: Optional["CuraApplication"] = None):
if cls.__instance is None: if cls.__instance is not None:
if application is None: raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
raise Exception("Upon first time creation, the application must be set.") if application is None:
cls.__instance = super(CuraAPI, cls).__new__(cls) raise RuntimeError("Upon first time creation, the application must be set.")
cls._application = application cls.__instance = super(CuraAPI, cls).__new__(cls)
cls._application = application
return cls.__instance return cls.__instance
def __init__(self, application: Optional["CuraApplication"] = None) -> None: def __init__(self, application: Optional["CuraApplication"] = None) -> None:

View file

@ -747,6 +747,11 @@ class MachineManager(QObject):
result = [] # type: List[str] result = [] # type: List[str]
for setting_instance in container.findInstances(): for setting_instance in container.findInstances():
setting_key = setting_instance.definition.key 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"): if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
continue continue

View file

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

View file

@ -4,7 +4,8 @@
from configparser import ConfigParser from configparser import ConfigParser
import zipfile import zipfile
import os 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 import xml.etree.ElementTree as ET
@ -732,7 +733,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
base_file_name = os.path.basename(file_name) base_file_name = os.path.basename(file_name)
self.setWorkspaceName(base_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): def _processQualityChanges(self, global_stack):
if self._machine_info.quality_changes_info is None: 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) version_config_parser.write(version_file_string)
archive.writestr(version_file, version_file_string.getvalue()) archive.writestr(version_file, version_file_string.getvalue())
self._writePluginMetadataToArchive(archive)
# Close the archive & reset states. # Close the archive & reset states.
archive.close() archive.close()
mesh_writer.setStoreArchive(False) mesh_writer.setStoreArchive(False)
return True 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. ## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive.
# \param container That follows the \type{ContainerInterface} to archive. # \param container That follows the \type{ContainerInterface} to archive.
# \param archive The archive to write to. # \param archive The archive to write to.

View file

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

View file

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

View file

@ -20,6 +20,8 @@ UM.Dialog{
maximumHeight: minimumHeight maximumHeight: minimumHeight
margin: 0 margin: 0
property string actionButtonText: subscribedPackagesModel.hasIncompatiblePackages && !subscribedPackagesModel.hasCompatiblePackages ? catalog.i18nc("@button", "Dismiss") : catalog.i18nc("@button", "Next")
Rectangle Rectangle
{ {
id: root id: root
@ -125,26 +127,6 @@ UM.Dialog{
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
elide: Text.ElideRight 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.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.margins: UM.Theme.getSize("default_margin").height anchors.margins: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@button", "Next") text: actionButtonText
onClicked: accept() onClicked: accept()
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
rightPadding: 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.Dialogs 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles // 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 minimumHeight: UM.Theme.getSize("license_window_minimum").height
width: minimumWidth width: minimumWidth
height: minimumHeight height: minimumHeight
backgroundColor: UM.Theme.getColor("main_background")
margin: screenScaleFactor * 10
Item ColumnLayout
{ {
anchors.fill: parent anchors.fill: parent
spacing: UM.Theme.getSize("thick_margin").height
UM.I18nCatalog{id: catalog; name: "cura"} UM.I18nCatalog{id: catalog; name: "cura"}
Label Label
{ {
id: licenseHeader id: licenseHeader
anchors.top: parent.top Layout.fillWidth: true
anchors.left: parent.left text: catalog.i18nc("@label", "You need to accept the license to install the package")
anchors.right: parent.right
text: licenseModel.headerText
wrapMode: Text.Wrap wrapMode: Text.Wrap
renderType: Text.NativeRendering 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 TextArea
{ {
id: licenseText id: licenseText
anchors.top: licenseHeader.bottom Layout.fillWidth: true
anchors.bottom: parent.bottom Layout.fillHeight: true
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.topMargin: UM.Theme.getSize("default_margin").height
readOnly: true readOnly: true
text: licenseModel.licenseText text: licenseModel.licenseText
@ -57,7 +90,7 @@ UM.Dialog
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
rightPadding: 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() } onClicked: { handler.onLicenseAccepted() }
} }
] ]
@ -67,7 +100,7 @@ UM.Dialog
Cura.SecondaryButton Cura.SecondaryButton
{ {
id: declineButton id: declineButton
text: catalog.i18nc("@button", "Decline and remove from account") text: licenseModel.declineButtonText
onClicked: { handler.onLicenseDeclined() } onClicked: { handler.onLicenseDeclined() }
} }
] ]

View file

@ -8,11 +8,12 @@ from UM import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Signal import Signal from UM.Signal import Signal
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication, ApplicationMetadata
from ..CloudApiModel import CloudApiModel from ..CloudApiModel import CloudApiModel
from .SubscribedPackagesModel import SubscribedPackagesModel from .SubscribedPackagesModel import SubscribedPackagesModel
from ..UltimakerCloudScope import UltimakerCloudScope from ..UltimakerCloudScope import UltimakerCloudScope
from typing import List, Dict, Any
class CloudPackageChecker(QObject): class CloudPackageChecker(QObject):
def __init__(self, application: CuraApplication) -> None: def __init__(self, application: CuraApplication) -> None:
@ -25,12 +26,12 @@ class CloudPackageChecker(QObject):
self._application.initializationFinished.connect(self._onAppInitialized) self._application.initializationFinished.connect(self._onAppInitialized)
self._i18n_catalog = i18nCatalog("cura") 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 a plugin, so most of the components required are not ready when
# this is initialized. Therefore, we wait until the application is ready. # this is initialized. Therefore, we wait until the application is ready.
def _onAppInitialized(self) -> None: def _onAppInitialized(self) -> None:
self._package_manager = self._application.getPackageManager() self._package_manager = self._application.getPackageManager()
# initial check # initial check
self._fetchUserSubscribedPackages() self._fetchUserSubscribedPackages()
# check again whenever the login state changes # check again whenever the login state changes
@ -38,25 +39,51 @@ class CloudPackageChecker(QObject):
def _fetchUserSubscribedPackages(self) -> None: def _fetchUserSubscribedPackages(self) -> None:
if self._application.getCuraAPI().account.isLoggedIn: if self._application.getCuraAPI().account.isLoggedIn:
self._getUserPackages() self._getUserSubscribedPackages()
def _handleCompatibilityData(self, json_data) -> None: def _getUserSubscribedPackages(self) -> None:
user_subscribed_packages = [plugin["package_id"] for plugin in json_data] 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() 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() user_dismissed_packages = self._package_manager.getDismissedPackages()
if user_dismissed_packages: if user_dismissed_packages:
user_installed_packages += 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)) 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: if package_discrepancy:
self._model.addDiscrepancies(package_discrepancy)
self._model.initialize(subscribed_packages_payload)
self._handlePackageDiscrepancies() self._handlePackageDiscrepancies()
def _handlePackageDiscrepancies(self) -> None: def _handlePackageDiscrepancies(self) -> None:
@ -76,35 +103,4 @@ class CloudPackageChecker(QObject):
def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None: def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
sync_message.hide() sync_message.hide()
self.discrepancies.emit(self._model) 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 assert self._dialog
self._dialog.accepted.connect(lambda: self._onConfirmClicked(model)) 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: 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. # For now, all compatible packages presented to the user should be installed.
# Later, we might remove items for which the user unselected the package # Later, we might remove items for which the user unselected the package
model.setItems(model.getCompatiblePackages()) if model.getCompatiblePackages():
self.packageMutations.emit(model) model.setItems(model.getCompatiblePackages())
self.packageMutations.emit(model)

View file

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

View file

@ -6,31 +6,52 @@ catalog = i18nCatalog("cura")
# Model for the ToolboxLicenseDialog # Model for the ToolboxLicenseDialog
class LicenseModel(QObject): class LicenseModel(QObject):
dialogTitleChanged = pyqtSignal() DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline")
headerChanged = pyqtSignal() ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree")
licenseTextChanged = pyqtSignal()
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__() super().__init__()
self._current_page_idx = 0 self._current_page_idx = 0
self._page_count = 1 self._page_count = 1
self._dialogTitle = "" self._dialogTitle = ""
self._header_text = ""
self._license_text = "" self._license_text = ""
self._package_name = "" 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) @pyqtProperty(str, notify=dialogTitleChanged)
def dialogTitle(self) -> str: def dialogTitle(self) -> str:
return self._dialogTitle return self._dialogTitle
@pyqtProperty(str, notify=headerChanged) @pyqtProperty(str, notify=packageNameChanged)
def headerText(self) -> str: def packageName(self) -> str:
return self._header_text return self._package_name
def setPackageName(self, name: str) -> None: 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._package_name = name
self.headerChanged.emit() 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) @pyqtProperty(str, notify=licenseTextChanged)
def licenseText(self) -> str: def licenseText(self) -> str:

View file

@ -17,6 +17,7 @@ class LicensePresenter(QObject):
def __init__(self, app: CuraApplication) -> None: def __init__(self, app: CuraApplication) -> None:
super().__init__() super().__init__()
self._catalog = i18nCatalog("cura")
self._dialog = None # type: Optional[QObject] self._dialog = None # type: Optional[QObject]
self._package_manager = app.getPackageManager() # type: PackageManager self._package_manager = app.getPackageManager() # type: PackageManager
# Emits List[Dict[str, [Any]] containing for example # Emits List[Dict[str, [Any]] containing for example
@ -25,7 +26,8 @@ class LicensePresenter(QObject):
self._current_package_idx = 0 self._current_package_idx = 0
self._package_models = [] # type: List[Dict] 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 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 ## 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 plugin_path: Root directory of the Toolbox plugin
# \param packages: Dict[package id, file path] # \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) path = os.path.join(plugin_path, self._compatibility_dialog_path)
self._initState(packages) self._initState(packages)
@ -42,7 +44,7 @@ class LicensePresenter(QObject):
if self._dialog is None: if self._dialog is None:
context_properties = { context_properties = {
"catalog": i18nCatalog("cura"), "catalog": self._catalog,
"licenseModel": self._license_model, "licenseModel": self._license_model,
"handler": self "handler": self
} }
@ -60,18 +62,20 @@ class LicensePresenter(QObject):
self._package_models[self._current_package_idx]["accepted"] = False self._package_models[self._current_package_idx]["accepted"] = False
self._checkNextPage() self._checkNextPage()
def _initState(self, packages: Dict[str, str]) -> None: def _initState(self, packages: Dict[str, Dict[str, str]]) -> None:
self._package_models = [ self._package_models = [
{ {
"package_id" : package_id, "package_id" : package_id,
"package_path" : package_path, "package_path" : item["package_path"],
"icon_url" : item["icon_url"],
"accepted" : None #: None: no answer yet "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: def _presentCurrentPackage(self) -> None:
package_model = self._package_models[self._current_package_idx] 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"]) license_content = self._package_manager.getPackageLicense(package_model["package_path"])
if license_content is None: if license_content is None:
# Implicitly accept when there is no license # Implicitly accept when there is no license
@ -79,7 +83,8 @@ class LicensePresenter(QObject):
return return
self._license_model.setCurrentPageIdx(self._current_package_idx) 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) self._license_model.setLicenseText(license_content)
if self._dialog: if self._dialog:
self._dialog.open() # Does nothing if already open self._dialog.open() # Does nothing if already open

View file

@ -37,27 +37,18 @@ class SubscribedPackagesModel(ListModel):
return True return True
return False 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: def addDiscrepancies(self, discrepancy: List[str]) -> None:
self._discrepancies = discrepancy self._discrepancies = discrepancy
def getCompatiblePackages(self): def getCompatiblePackages(self) -> List[Dict[str, Any]]:
return [x for x in self._items if x["is_compatible"]] 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() self._items.clear()
for item in self._metadata: for item in subscribed_packages_payload:
if item["package_id"] not in self._discrepancies: if item["package_id"] not in self._discrepancies:
continue continue
package = { package = {

View file

@ -63,9 +63,9 @@ class SyncOrchestrator(Extension):
self._download_presenter.download(mutations) self._download_presenter.download(mutations)
## Called when a set of packages have finished downloading ## 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] # \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: if error_items:
message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items))) message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items)))
self._showErrorMessage(message) self._showErrorMessage(message)

View file

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

View file

@ -5984,7 +5984,7 @@
"print_sequence": "print_sequence":
{ {
"label": "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", "type": "enum",
"options": "options":
{ {

View file

@ -18,10 +18,10 @@
"default_value": "skirt" "default_value": "skirt"
}, },
"bottom_thickness": { "bottom_thickness": {
"value": "0.5" "value": "0.6"
}, },
"brim_width": { "brim_width": {
"value": "2.0" "value": "3.0"
}, },
"cool_fan_enabled": { "cool_fan_enabled": {
"value": "True" "value": "True"
@ -39,19 +39,28 @@
"value": "True" "value": "True"
}, },
"cool_min_layer_time": { "cool_min_layer_time": {
"value": "5.0" "value": "1.0"
}, },
"cool_min_speed": { "cool_min_speed": {
"value": "10.0" "value": "5.0"
}, },
"infill_before_walls": { "infill_before_walls": {
"value": "True" "value": "True"
}, },
"infill_line_width": {
"value": "0.6"
},
"infill_overlap": { "infill_overlap": {
"value": "15.0" "value": "15.0"
}, },
"infill_sparse_density": {
"value": "26.0"
},
"ironing_enabled": {
"value": "True"
},
"layer_0_z_overlap": { "layer_0_z_overlap": {
"value": "0.22" "value": "0.11"
}, },
"layer_height_0": { "layer_height_0": {
"value": "0.3" "value": "0.3"
@ -60,11 +69,23 @@
"value": "100" "value": "100"
}, },
"machine_end_gcode": { "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": { "machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)" "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": { "machine_height": {
"value": "100" "value": "100"
}, },
@ -72,7 +93,7 @@
"default_value": "Renkforce RF100" "default_value": "Renkforce RF100"
}, },
"machine_start_gcode": { "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": { "machine_width": {
"value": "100" "value": "100"
@ -90,7 +111,7 @@
"value": "True" "value": "True"
}, },
"raft_airgap": { "raft_airgap": {
"value": "0.22" "value": "0.33"
}, },
"raft_base_line_spacing": { "raft_base_line_spacing": {
"value": "3.0" "value": "3.0"
@ -111,22 +132,25 @@
"value": "0.27" "value": "0.27"
}, },
"raft_margin": { "raft_margin": {
"value": "5.0" "value": "6.0"
},
"raft_speed": {
"value": "20.0"
}, },
"raft_surface_layers": { "raft_surface_layers": {
"value": "2.0" "value": "2"
}, },
"raft_surface_line_spacing": { "raft_surface_line_spacing": {
"value": "3.0" "value": "0.4"
}, },
"raft_surface_line_width": { "raft_surface_line_width": {
"value": "0.4" "value": "0.4"
}, },
"raft_surface_thickness": { "raft_surface_thickness": {
"value": "0.27" "value": "0.1"
}, },
"retraction_amount": { "retraction_amount": {
"value": "2.0" "value": "5.0"
}, },
"retraction_combing": { "retraction_combing": {
"default_value": "all" "default_value": "all"
@ -134,15 +158,12 @@
"retraction_enable": { "retraction_enable": {
"value": "True" "value": "True"
}, },
"retraction_hop_enabled": { "retraction_hop": {
"value": "1.0" "value": "1.0"
}, },
"retraction_min_travel": { "retraction_min_travel": {
"value": "1.5" "value": "1.5"
}, },
"retraction_speed": {
"value": "40.0"
},
"skin_overlap": { "skin_overlap": {
"value": "15.0" "value": "15.0"
}, },
@ -185,6 +206,9 @@
"support_infill_rate": { "support_infill_rate": {
"value": "15 if support_enable else 0 if support_tree_enable else 15" "value": "15 if support_enable else 0 if support_tree_enable else 15"
}, },
"support_line_width": {
"value": "0.6"
},
"support_pattern": { "support_pattern": {
"default_value": "lines" "default_value": "lines"
}, },
@ -192,13 +216,13 @@
"default_value": "everywhere" "default_value": "everywhere"
}, },
"support_xy_distance": { "support_xy_distance": {
"value": "0.5" "value": "0.7"
}, },
"support_z_distance": { "support_z_distance": {
"value": "0.1" "value": "0.35"
}, },
"top_thickness": { "top_bottom_thickness": {
"value": "0.5" "value": "0.8"
}, },
"wall_thickness": { "wall_thickness": {
"value": "0.8" "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 icon: StandardIcon.Question
onYes: onYes:
{ {
CuraApplication.deleteAll(); CuraApplication.resetWorkspace()
Cura.Actions.resetProfile.trigger(); Cura.Actions.resetProfile.trigger()
UM.Controller.setActiveStage("PrepareStage") UM.Controller.setActiveStage("PrepareStage")
} }
} }

View file

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

View file

@ -202,8 +202,9 @@ Item
// dragging a tool handle. // dragging a tool handle.
Rectangle Rectangle
{ {
x: -base.x + base.mouseX + UM.Theme.getSize("default_margin").width id: toolInfo
y: -base.y + base.mouseY + UM.Theme.getSize("default_margin").height 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 width: toolHint.width + UM.Theme.getSize("default_margin").width
height: toolHint.height; height: toolHint.height;