Merge remote-tracking branch 'upstream/master' into dagoma_discoultimate

This commit is contained in:
Orel 2020-02-04 10:07:56 +01:00
commit 173a05ddb5
71 changed files with 1912 additions and 299 deletions

View File

@ -56,6 +56,13 @@ function(cura_add_test)
endif()
endfunction()
#Add test for import statements which are not compatible with all builds
add_test(
NAME "invalid-imports"
COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
file(GLOB_RECURSE _plugins plugins/*/__init__.py)

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.
# 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 application is None:
raise Exception("Upon first time creation, the application must be set.")
cls.__instance = super(CuraAPI, cls).__new__(cls)
cls._application = application
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 RuntimeError("Upon first time creation, the application must be set.")
cls.__instance = super(CuraAPI, cls).__new__(cls)
cls._application = application
return cls.__instance
def __init__(self, application: Optional["CuraApplication"] = None) -> None:

View File

@ -145,6 +145,14 @@ class Backup:
# \return Whether we had success or not.
@staticmethod
def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
# Implement security recommendations: Sanity check on zip files will make it harder to spoof.
from cura.CuraApplication import CuraApplication
config_filename = CuraApplication.getInstance().getApplicationName() + ".cfg" # Should be there if valid.
if config_filename not in [file.filename for file in archive.filelist]:
Logger.logException("e", "Unable to extract the backup due to corruption of compressed file(s).")
return False
Logger.log("d", "Removing current data in location: %s", target_path)
Resources.factoryReset()
Logger.log("d", "Extracting backup to location: %s", target_path)

View File

@ -1,4 +1,4 @@
# Copyright (c) 2019 Ultimaker B.V.
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
@ -191,8 +191,6 @@ class CuraApplication(QtApplication):
self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions]
self._cura_package_manager = None
self._machine_action_manager = None # type: Optional[MachineActionManager.MachineActionManager]
self.empty_container = None # type: EmptyInstanceContainer
@ -632,6 +630,12 @@ class CuraApplication(QtApplication):
def showPreferences(self) -> None:
self.showPreferencesWindow.emit()
# This is called by drag-and-dropping curapackage files.
@pyqtSlot(QUrl)
def installPackageViaDragAndDrop(self, file_url: str) -> Optional[str]:
filename = QUrl(file_url).toLocalFile()
return self._package_manager.installPackage(filename)
@override(Application)
def getGlobalContainerStack(self) -> Optional["GlobalStack"]:
return self._global_container_stack
@ -1827,15 +1831,21 @@ class CuraApplication(QtApplication):
def _onContextMenuRequested(self, x: float, y: float) -> None:
# Ensure we select the object if we request a context menu over an object without having a selection.
if not Selection.hasSelection():
node = self.getController().getScene().findObject(cast(SelectionPass, self.getRenderer().getRenderPass("selection")).getIdAtPosition(x, y))
if node:
parent = node.getParent()
while(parent and parent.callDecoration("isGroup")):
node = parent
parent = node.getParent()
if Selection.hasSelection():
return
selection_pass = cast(SelectionPass, self.getRenderer().getRenderPass("selection"))
if not selection_pass: # If you right-click before the rendering has been initialised there might not be a selection pass yet.
print("--------------ding! Got the crash.")
return
node = self.getController().getScene().findObject(selection_pass.getIdAtPosition(x, y))
if not node:
return
parent = node.getParent()
while parent and parent.callDecoration("isGroup"):
node = parent
parent = node.getParent()
Selection.add(node)
Selection.add(node)
@pyqtSlot()
def showMoreInformationDialogForAnonymousDataCollection(self):

View File

@ -115,9 +115,10 @@ class AuthorizationHelpers:
)
@staticmethod
## Generate a 16-character verification code.
# \param code_length: How long should the code be?
def generateVerificationCode(code_length: int = 16) -> str:
## Generate a verification code of arbitrary length.
# \param code_length: How long should the code be? This should never be lower than 16, but it's probably better to
# leave it at 32
def generateVerificationCode(code_length: int = 32) -> str:
return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
@staticmethod

View File

@ -25,6 +25,8 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
self.authorization_callback = None # type: Optional[Callable[[AuthenticationResponse], None]]
self.verification_code = None # type: Optional[str]
self.state = None # type: Optional[str]
# CURA-6609: Some browser seems to issue a HEAD instead of GET request as the callback.
def do_HEAD(self) -> None:
self.do_GET()
@ -58,7 +60,14 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
# \return HTTP ResponseData containing a success page to show to the user.
def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
code = self._queryGet(query, "code")
if code and self.authorization_helpers is not None and self.verification_code is not None:
state = self._queryGet(query, "state")
if state != self.state:
token_response = AuthenticationResponse(
success = False,
err_message=catalog.i18nc("@message",
"The provided state is not correct.")
)
elif code and self.authorization_helpers is not None and self.verification_code is not None:
# If the code was returned we get the access token.
token_response = self.authorization_helpers.getAccessTokenUsingAuthorizationCode(
code, self.verification_code)

View File

@ -25,3 +25,6 @@ class AuthorizationRequestServer(HTTPServer):
## Set the verification code on the request handler.
def setVerificationCode(self, verification_code: str) -> None:
self.RequestHandlerClass.verification_code = verification_code # type: ignore
def setState(self, state: str) -> None:
self.RequestHandlerClass.state = state # type: ignore

View File

@ -153,13 +153,15 @@ class AuthorizationService:
verification_code = self._auth_helpers.generateVerificationCode()
challenge_code = self._auth_helpers.generateVerificationCodeChallenge(verification_code)
state = AuthorizationHelpers.generateVerificationCode()
# Create the query string needed for the OAuth2 flow.
query_string = urlencode({
"client_id": self._settings.CLIENT_ID,
"redirect_uri": self._settings.CALLBACK_URL,
"scope": self._settings.CLIENT_SCOPES,
"response_type": "code",
"state": "(.Y.)",
"state": state, # Forever in our Hearts, RIP "(.Y.)" (2018-2020)
"code_challenge": challenge_code,
"code_challenge_method": "S512"
})
@ -168,7 +170,7 @@ class AuthorizationService:
QDesktopServices.openUrl(QUrl("{}?{}".format(self._auth_url, query_string)))
# Start a local web server to receive the callback URL on.
self._server.start(verification_code)
self._server.start(verification_code, state)
## Callback method for the authentication flow.
def _onAuthStateChanged(self, auth_response: AuthenticationResponse) -> None:

View File

@ -36,7 +36,8 @@ class LocalAuthorizationServer:
## Starts the local web server to handle the authorization callback.
# \param verification_code The verification code part of the OAuth2 client identification.
def start(self, verification_code: str) -> None:
# \param state The unique state code (to ensure that the request we get back is really from the server.
def start(self, verification_code: str, state: str) -> None:
if self._web_server:
# If the server is already running (because of a previously aborted auth flow), we don't have to start it.
# We still inject the new verification code though.
@ -53,6 +54,7 @@ class LocalAuthorizationServer:
self._web_server.setAuthorizationHelpers(self._auth_helpers)
self._web_server.setAuthorizationCallback(self._auth_state_changed_callback)
self._web_server.setVerificationCode(verification_code)
self._web_server.setState(state)
# Start the server on a new thread.
self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)

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

@ -13,6 +13,8 @@ export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}"
cd "${PROJECT_DIR}"
#
# Clone Uranium and set PYTHONPATH first
#

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,18 +72,25 @@ 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:
display_text = display_text + "!"
if not self.getSettingValueByKey("scroll"):
display_text = display_text + " " + name + "!"
else:
display_text = display_text + "!"
line_index = lines.index(line)
lines.insert(line_index + 1, display_text)
i += 1

View File

@ -2,6 +2,7 @@
from ..Script import Script
class TimeLapse(Script):
def __init__(self):
super().__init__()
@ -75,21 +76,29 @@ class TimeLapse(Script):
trigger_command = self.getSettingValueByKey("trigger_command")
pause_length = self.getSettingValueByKey("pause_length")
gcode_to_append = ";TimeLapse Begin\n"
last_x = 0
last_y = 0
if park_print_head:
gcode_to_append += self.putValue(G = 1, F = feed_rate, X = x_park, Y = y_park) + " ;Park print head\n"
gcode_to_append += self.putValue(M = 400) + " ;Wait for moves to finish\n"
gcode_to_append += self.putValue(G=1, F=feed_rate,
X=x_park, Y=y_park) + " ;Park print head\n"
gcode_to_append += self.putValue(M=400) + " ;Wait for moves to finish\n"
gcode_to_append += trigger_command + " ;Snap Photo\n"
gcode_to_append += self.putValue(G = 4, P = pause_length) + " ;Wait for camera\n"
gcode_to_append += ";TimeLapse End\n"
for layer in data:
gcode_to_append += self.putValue(G=4, P=pause_length) + " ;Wait for camera\n"
for idx, layer in enumerate(data):
for line in layer.split("\n"):
if self.getValue(line, "G") in {0, 1}: # Track X,Y location.
last_x = self.getValue(line, "X", last_x)
last_y = self.getValue(line, "Y", last_y)
# Check that a layer is being printed
lines = layer.split("\n")
for line in lines:
if ";LAYER:" in line:
index = data.index(layer)
layer += gcode_to_append
data[index] = layer
layer += "G0 X%s Y%s\n" % (last_x, last_y)
data[idx] = layer
break
return data

View File

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import sys
@ -116,8 +116,9 @@ class SimulationView(CuraView):
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
self._compatibility_mode = self._evaluateCompatibilityMode()
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled."),
title = catalog.i18nc("@info:title", "Simulation View"))
self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."), title = catalog.i18nc("@info:title", "No layer data"))
QtApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
@ -149,6 +150,7 @@ class SimulationView(CuraView):
if self._activity == activity:
return
self._activity = activity
self._updateSliceWarningVisibility()
self.activityChanged.emit()
def getSimulationPass(self) -> SimulationPass:
@ -543,11 +545,13 @@ class SimulationView(CuraView):
self._composite_pass.getLayerBindings().append("simulationview")
self._old_composite_shader = self._composite_pass.getCompositeShader()
self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
self._updateSliceWarningVisibility()
elif event.type == Event.ViewDeactivateEvent:
self._controller.getScene().getRoot().childrenChanged.disconnect(self._onSceneChanged)
Application.getInstance().getPreferences().preferenceChanged.disconnect(self._onPreferencesChanged)
self._wireprint_warning_message.hide()
self._slice_first_warning_message.hide()
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
@ -661,6 +665,12 @@ class SimulationView(CuraView):
self._updateWithPreferences()
def _updateSliceWarningVisibility(self):
if not self.getActivity():
self._slice_first_warning_message.show()
else:
self._slice_first_warning_message.hide()
class _CreateTopLayersJob(Job):
def __init__(self, scene: "Scene", layer_number: int, solid_layers: int) -> None:

View File

@ -72,6 +72,7 @@ Window
right: parent.right
}
text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
color: UM.Theme.getColor("text")
wrapMode: Text.WordWrap
renderType: Text.NativeRendering
}

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
@ -90,7 +92,7 @@ UM.Dialog{
Label
{
font: UM.Theme.getFont("default")
text: catalog.i18nc("@label", "The following packages can not be installed because of incompatible Cura version:")
text: catalog.i18nc("@label", "The following packages can not be installed because of an incompatible Cura version:")
visible: subscribedPackagesModel.hasIncompatiblePackages
color: UM.Theme.getColor("text")
height: contentHeight + UM.Theme.getSize("default_margin").height
@ -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)
}
}
}
}
}
}
@ -152,14 +134,16 @@ UM.Dialog{
} // End of ScrollView
Cura.ActionButton
Cura.PrimaryButton
{
id: nextButton
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

@ -4,12 +4,12 @@
import QtQuick 2.10
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
import UM 1.1 as UM
import Cura 1.6 as Cura
UM.Dialog
{
@ -19,50 +19,90 @@ 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")
color: UM.Theme.getColor("text")
wrapMode: Text.Wrap
renderType: Text.NativeRendering
}
TextArea
{
id: licenseText
anchors.top: licenseHeader.bottom
anchors.bottom: parent.bottom
Row {
id: packageRow
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: UM.Theme.getSize("default_margin").height
readOnly: true
text: licenseModel.licenseText
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
color: UM.Theme.getColor("text")
font.bold: true
anchors.verticalCenter: icon.verticalCenter
height: contentHeight
wrapMode: Text.Wrap
renderType: Text.NativeRendering
}
}
Cura.ScrollableTextArea
{
Layout.fillWidth: true
Layout.fillHeight: true
anchors.topMargin: UM.Theme.getSize("default_margin").height
textArea.text: licenseModel.licenseText
textArea.readOnly: true
}
}
rightButtons:
[
Button
Cura.PrimaryButton
{
id: acceptButton
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@action:button", "Accept")
leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width
rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width
text: licenseModel.acceptButtonText
onClicked: { handler.onLicenseAccepted() }
},
Button
}
]
leftButtons:
[
Cura.SecondaryButton
{
id: declineButton
anchors.margins: UM.Theme.getSize("default_margin").width
text: catalog.i18nc("@action:button", "Decline")
text: licenseModel.declineButtonText
onClicked: { handler.onLicenseDeclined() }
}
]

View File

@ -18,3 +18,11 @@ class CloudApiModel:
cloud_api_root=cloud_api_root,
cloud_api_version=cloud_api_version,
)
## https://api.ultimaker.com/cura-packages/v1/user/packages/{package_id}
@classmethod
def userPackageUrl(cls, package_id: str) -> str:
return (CloudApiModel.api_url_user_packages + "/{package_id}").format(
package_id=package_id
)

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:
@ -64,7 +91,6 @@ class CloudPackageChecker(QObject):
sync_message = Message(self._i18n_catalog.i18nc(
"@info:generic",
"\nDo you want to sync material and software packages with your account?"),
lifetime=0,
title=self._i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
sync_message.addAction("sync",
name=self._i18n_catalog.i18nc("@action:button", "Sync"),
@ -76,35 +102,4 @@ 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")
self.discrepancies.emit(self._model)

View File

@ -1,18 +1,51 @@
from UM.Logger import Logger
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
from cura.CuraApplication import CuraApplication
from ..CloudApiModel import CloudApiModel
from ..UltimakerCloudScope import UltimakerCloudScope
## Manages Cloud subscriptions. When a package is added to a user's account, the user is 'subscribed' to that package
# Whenever the user logs in on another instance of Cura, these subscriptions can be used to sync the user's plugins
class CloudPackageManager:
def __init__(self, app: CuraApplication) -> None:
self._request_manager = app.getHttpRequestManager()
self._scope = UltimakerCloudScope(app)
"""Manages Cloud subscriptions
def subscribe(self, package_id: str) -> None:
When a package is added to a user's account, the user is 'subscribed' to that package.
Whenever the user logs in on another instance of Cura, these subscriptions can be used to sync the user's plugins
Singleton: use CloudPackageManager.getInstance() instead of CloudPackageManager()
"""
__instance = None
@classmethod
def getInstance(cls, app: CuraApplication):
if not cls.__instance:
cls.__instance = CloudPackageManager(app)
return cls.__instance
def __init__(self, app: CuraApplication) -> None:
if self.__instance is not None:
raise RuntimeError("This is a Singleton. use getInstance()")
self._scope = UltimakerCloudScope(app) # type: UltimakerCloudScope
app.getPackageManager().packageInstalled.connect(self._onPackageInstalled)
def unsubscribe(self, package_id: str) -> None:
url = CloudApiModel.userPackageUrl(package_id)
HttpRequestManager.getInstance().delete(url = url, scope = self._scope)
def _subscribe(self, package_id: str) -> None:
"""You probably don't want to use this directly. All installed packages will be automatically subscribed."""
Logger.debug("Subscribing to {}", package_id)
data = "{\"data\": {\"package_id\": \"%s\", \"sdk_version\": \"%s\"}}" % (package_id, CloudApiModel.sdk_version)
self._request_manager.put(url=CloudApiModel.api_url_user_packages,
data=data.encode(),
scope=self._scope
)
HttpRequestManager.getInstance().put(
url = CloudApiModel.api_url_user_packages,
data = data.encode(),
scope = self._scope
)
def _onPackageInstalled(self, package_id: str):
if CuraApplication.getInstance().getCuraAPI().account.isLoggedIn:
# We might already be subscribed, but checking would take one extra request. Instead, simply subscribe
self._subscribe(package_id)

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
model.setItems(model.getCompatiblePackages())
self.packageMutations.emit(model)
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:
@ -50,6 +71,7 @@ class LicenseModel(QObject):
self._updateDialogTitle()
def _updateDialogTitle(self):
self._dialogTitle = catalog.i18nc("@title:window", "Plugin License Agreement ({}/{})"
.format(self._current_page_idx + 1, self._page_count))
self._dialogTitle = catalog.i18nc("@title:window", "Plugin License Agreement")
if self._page_count > 1:
self._dialogTitle = self._dialogTitle + " ({}/{})".format(self._current_page_idx + 1, self._page_count)
self.dialogTitleChanged.emit()

View File

@ -1,5 +1,6 @@
import os
from typing import Dict, Optional, List
from collections import OrderedDict
from typing import Dict, Optional, List, Any
from PyQt5.QtCore import QObject, pyqtSlot
@ -17,6 +18,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 +27,9 @@ 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._page_count = 0
self._app = app
@ -34,7 +38,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,12 +46,11 @@ class LicensePresenter(QObject):
if self._dialog is None:
context_properties = {
"catalog": i18nCatalog("cura"),
"catalog": self._catalog,
"licenseModel": self._license_model,
"handler": self
}
self._dialog = self._app.createQmlComponent(path, context_properties)
self._license_model.setPageCount(len(self._package_models))
self._presentCurrentPackage()
@pyqtSlot()
@ -60,32 +63,41 @@ class LicensePresenter(QObject):
self._package_models[self._current_package_idx]["accepted"] = False
self._checkNextPage()
def _initState(self, packages: Dict[str, str]) -> None:
self._package_models = [
{
"package_id" : package_id,
"package_path" : package_path,
"accepted" : None #: None: no answer yet
}
for package_id, package_path in packages.items()
]
def _initState(self, packages: Dict[str, Dict[str, str]]) -> None:
implicitly_accepted_count = 0
for package_id, item in packages.items():
item["package_id"] = package_id
item["licence_content"] = self._package_manager.getPackageLicense(item["package_path"])
if item["licence_content"] is None:
# Implicitly accept when there is no license
item["accepted"] = True
implicitly_accepted_count = implicitly_accepted_count + 1
self._package_models.append(item)
else:
item["accepted"] = None #: None: no answer yet
# When presenting the packages, we want to show packages which have a license first.
# In fact, we don't want to show the others at all because they are implicitly accepted
self._package_models.insert(0, item)
CuraApplication.getInstance().processEvents()
self._page_count = len(self._package_models) - implicitly_accepted_count
self._license_model.setPageCount(self._page_count)
def _presentCurrentPackage(self) -> None:
package_model = self._package_models[self._current_package_idx]
license_content = self._package_manager.getPackageLicense(package_model["package_path"])
if license_content is None:
# Implicitly accept when there is no license
self.onLicenseAccepted()
return
package_info = self._package_manager.getPackageInfo(package_model["package_path"])
self._license_model.setCurrentPageIdx(self._current_package_idx)
self._license_model.setPackageName(package_model["package_id"])
self._license_model.setLicenseText(license_content)
self._license_model.setPackageName(package_info["display_name"])
self._license_model.setIconUrl(package_model["icon_url"])
self._license_model.setLicenseText(package_model["licence_content"])
if self._dialog:
self._dialog.open() # Does nothing if already open
def _checkNextPage(self) -> None:
if self._current_package_idx + 1 < len(self._package_models):
if self._current_package_idx + 1 < self._page_count:
self._current_package_idx += 1
self._presentCurrentPackage()
else:

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

@ -1,8 +1,10 @@
import os
from typing import List, Dict, Any, cast
from UM import i18n_catalog
from UM.Extension import Extension
from UM.Logger import Logger
from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
from cura.CuraApplication import CuraApplication
from .CloudPackageChecker import CloudPackageChecker
@ -36,7 +38,8 @@ class SyncOrchestrator(Extension):
self._name = "SyncOrchestrator"
self._package_manager = app.getPackageManager()
self._cloud_package_manager = CloudPackageManager(app)
# Keep a reference to the CloudPackageManager. it watches for installed packages and subscribes to them
self._cloud_package_manager = CloudPackageManager.getInstance(app) # type: CloudPackageManager
self._checker = CloudPackageChecker(app) # type: CloudPackageChecker
self._checker.discrepancies.connect(self._onDiscrepancies)
@ -61,32 +64,37 @@ 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:
# todo handle error items
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)
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId()))
self._license_presenter.present(plugin_path, success_items)
# Called when user has accepted / declined all licenses for the downloaded packages
def _onLicenseAnswers(self, answers: List[Dict[str, Any]]) -> None:
Logger.debug("Got license answers: {}", answers)
has_changes = False # True when at least one package is installed
for item in answers:
if item["accepted"]:
# install and subscribe packages
if not self._package_manager.installPackage(item["package_path"]):
Logger.error("could not install {}".format(item["package_id"]))
message = "Could not install {}".format(item["package_id"])
self._showErrorMessage(message)
continue
self._cloud_package_manager.subscribe(item["package_id"])
has_changes = True
else:
# todo unsubscribe declined packages
pass
self._cloud_package_manager.unsubscribe(item["package_id"])
# delete temp file
os.remove(item["package_path"])
if has_changes:
self._restart_presenter.present()
## Logs an error and shows it to the user
def _showErrorMessage(self, text: str):
Logger.error(text)
Message(text, lifetime=0).show()

View File

@ -21,7 +21,6 @@ from cura.Machines.ContainerTree import ContainerTree
from .CloudApiModel import CloudApiModel
from .AuthorsModel import AuthorsModel
from .CloudSync.CloudPackageManager import CloudPackageManager
from .CloudSync.LicenseModel import LicenseModel
from .PackagesModel import PackagesModel
from .UltimakerCloudScope import UltimakerCloudScope
@ -44,7 +43,6 @@ class Toolbox(QObject, Extension):
self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int]
# Network:
self._cloud_package_manager = CloudPackageManager(application) # type: CloudPackageManager
self._download_request_data = None # type: Optional[HttpRequestData]
self._download_progress = 0 # type: float
self._is_downloading = False # type: bool
@ -147,17 +145,14 @@ class Toolbox(QObject, Extension):
self._application.getHttpRequestManager().put(url, data = data.encode(), scope = self._scope)
@pyqtSlot(str)
def subscribe(self, package_id: str) -> None:
self._cloud_package_manager.subscribe(package_id)
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)
@ -376,7 +371,6 @@ class Toolbox(QObject, Extension):
def onLicenseAccepted(self):
self.closeLicenseDialog.emit()
package_id = self.install(self.getLicenseDialogPluginFileLocation())
self.subscribe(package_id)
@pyqtSlot()
@ -670,14 +664,16 @@ 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))
# Getter & Setters for Properties:
# --------------------------------------------------------------------------
@ -699,14 +695,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

@ -6,6 +6,9 @@ from cura.API import Account
from cura.CuraApplication import CuraApplication
## Add a Authorization header to the request for Ultimaker Cloud Api requests.
# When the user is not logged in or a token is not available, a warning will be logged
# Also add the user agent headers (see DefaultUserAgentScope)
class UltimakerCloudScope(DefaultUserAgentScope):
def __init__(self, application: CuraApplication):
super().__init__(application)

View File

@ -848,6 +848,23 @@
"website": "https://ultimaker.com"
}
}
},
"VersionUpgrade44to45": {
"package_info": {
"package_id": "VersionUpgrade44to45",
"package_type": "plugin",
"display_name": "Version Upgrade 4.4 to 4.5",
"description": "Upgrades configurations from Cura 4.4 to Cura 4.5.",
"package_version": "1.0.0",
"sdk_version": "7.0.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "UltimakerPackages",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"X3DReader": {
"package_info": {

View File

@ -2122,6 +2122,7 @@
"default_value": 210,
"minimum_value_warning": "0",
"maximum_value_warning": "285",
"maximum_value": "365",
"enabled": false,
"settable_per_extruder": true,
"settable_per_mesh": false,
@ -2153,6 +2154,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "0",
"maximum_value_warning": "285",
"maximum_value": "365",
"enabled": "machine_nozzle_temp_enabled and not (material_flow_dependent_temperature)",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -2168,6 +2170,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "0",
"maximum_value_warning": "285",
"maximum_value": "365",
"enabled": "machine_nozzle_temp_enabled",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -2183,6 +2186,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "material_standby_temperature",
"maximum_value_warning": "material_print_temperature",
"maximum_value": "365",
"enabled": "machine_nozzle_temp_enabled and not machine_extruders_share_heater",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -2198,6 +2202,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "material_standby_temperature",
"maximum_value_warning": "material_print_temperature",
"maximum_value": "365",
"enabled": "machine_nozzle_temp_enabled and not machine_extruders_share_heater",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -2227,6 +2232,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "build_volume_temperature",
"maximum_value_warning": "130",
"maximum_value": "200",
"enabled": false,
"settable_per_mesh": false,
"settable_per_extruder": false,
@ -2244,6 +2250,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "build_volume_temperature",
"maximum_value_warning": "130",
"maximum_value": "200",
"enabled": "machine_heated_bed and machine_gcode_flavor != \"UltiGCode\"",
"settable_per_mesh": false,
"settable_per_extruder": false,
@ -2261,6 +2268,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "max(build_volume_temperature, max(extruderValues('material_bed_temperature')))",
"maximum_value_warning": "130",
"maximum_value": "200",
"enabled": "machine_heated_bed and machine_gcode_flavor != \"UltiGCode\"",
"settable_per_mesh": false,
"settable_per_extruder": false,
@ -2377,6 +2385,7 @@
"enabled": false,
"minimum_value": "-273.15",
"maximum_value_warning": "300",
"maximum_value": "365",
"settable_per_mesh": false,
"settable_per_extruder": true
},
@ -2415,6 +2424,7 @@
"default_value": 50,
"enabled": false,
"minimum_value": "-273.15",
"maximum_value": "365",
"maximum_value_warning": "300",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -2691,6 +2701,7 @@
"minimum_value": "-273.15",
"minimum_value_warning": "0",
"maximum_value_warning": "260",
"maximum_value": "365",
"enabled": "extruders_enabled_count > 1 and machine_nozzle_temp_enabled",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -5688,7 +5699,7 @@
"unit": "mm",
"enabled": "resolveOrValue('prime_tower_enable')",
"default_value": 200,
"value": "machine_width - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' or (prime_tower_brim_enable and adhesion_type != 'raft') else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - 1",
"value": "machine_width - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' or (prime_tower_brim_enable and adhesion_type != 'raft') else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - max(map(abs, extruderValues('machine_nozzle_offset_x'))) - 1",
"maximum_value": "machine_width / 2 if machine_center_is_zero else machine_width",
"minimum_value": "resolveOrValue('prime_tower_size') - machine_width / 2 if machine_center_is_zero else resolveOrValue('prime_tower_size')",
"settable_per_mesh": false,
@ -5702,7 +5713,7 @@
"unit": "mm",
"enabled": "resolveOrValue('prime_tower_enable')",
"default_value": 200,
"value": "machine_depth - prime_tower_size - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' or (prime_tower_brim_enable and adhesion_type != 'raft') else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - 1",
"value": "machine_depth - prime_tower_size - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' or (prime_tower_brim_enable and adhesion_type != 'raft') else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - max(map(abs, extruderValues('machine_nozzle_offset_y'))) - 1",
"maximum_value": "machine_depth / 2 - resolveOrValue('prime_tower_size') if machine_center_is_zero else machine_depth - resolveOrValue('prime_tower_size')",
"minimum_value": "machine_depth / -2 if machine_center_is_zero else 0",
"settable_per_mesh": false,
@ -5984,7 +5995,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":
{
@ -6666,7 +6677,17 @@
"limit_to_extruder": "wall_0_extruder_nr",
"settable_per_mesh": true
},
"magic_fuzzy_skin_thickness":
"magic_fuzzy_skin_outside_only":
{
"label": "Fuzzy Skin Outside Only",
"description": "Jitter only the parts' outlines and not the parts' holes.",
"type": "bool",
"default_value": false,
"enabled": "magic_fuzzy_skin_enabled",
"limit_to_extruder": "wall_0_extruder_nr",
"settable_per_mesh": true
},
"magic_fuzzy_skin_thickness":
{
"label": "Fuzzy Skin Thickness",
"description": "The width within which to jitter. It's advised to keep this below the outer wall width, since the inner walls are unaltered.",

View File

@ -0,0 +1,57 @@
{
"version": 2,
"name": "Geeetech A10",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Amit L",
"manufacturer": "Geeetech",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains":
{
"0": "geeetech_A10_1"
}
},
"overrides": {
"machine_name": { "default_value": "Geeetech A10" },
"machine_width": {
"default_value": 220
},
"machine_height": {
"default_value": 220
},
"machine_depth": {
"default_value": 260
}, "machine_center_is_zero": {
"default_value": false
},
"layer_height": { "default_value": 0.1 },
"layer_height_0": { "default_value": 0.15 },
"retraction_amount": { "default_value": 0.8 },
"retraction_speed": { "default_value": 35 },
"adhesion_type": { "default_value": "skirt" },
"machine_head_with_fans_polygon": { "default_value": [[-31,31],[34,31],[34,-40],[-31,-40]] },
"gantry_height": { "value": "28" },
"machine_max_feedrate_z": { "default_value": 12 },
"machine_max_feedrate_e": { "default_value": 120 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"machine_max_jerk_z": { "default_value": 0.2 },
"machine_max_jerk_e": { "default_value": 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": "G28 \nG1 Z15 F300\nM107\nG90\nM82\nM104 S215\nM140 S55\nG92 E0\nM109 S215\nM107\nG0 X10 Y20 F6000\nG1 Z0.8\nG1 F300 X180 E40\nG1 F1200 Z2\nG92 E0\nG28"
},
"machine_end_gcode": {
"default_value": "G91\nG1 E-1\nG0 X0 Y200\nM104 S0\nG90\nG92 E0\nM140 S0\nM84\nM104 S0\nM140 S0\nM84"
},
"machine_extruder_count": {
"default_value": 1
}
}
}

View File

@ -0,0 +1,58 @@
{
"version": 2,
"name": "Geeetech A10M",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Amit L",
"manufacturer": "Geeetech",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains":
{
"0": "geeetech_A10M_1",
"1": "geeetech_A10M_2"
}
},
"overrides": {
"machine_name": { "default_value": "Geeetech A10M" },
"machine_width": {
"default_value": 220
},
"machine_height": {
"default_value": 220
},
"machine_depth": {
"default_value": 260
}, "machine_center_is_zero": {
"default_value": false
},
"layer_height": { "default_value": 0.1 },
"layer_height_0": { "default_value": 0.15 },
"retraction_amount": { "default_value": 0.8 },
"retraction_speed": { "default_value": 35 },
"adhesion_type": { "default_value": "skirt" },
"machine_head_with_fans_polygon": { "default_value": [[-31,31],[34,31],[34,-40],[-31,-40]] },
"gantry_height": { "value": "28" },
"machine_max_feedrate_z": { "default_value": 12 },
"machine_max_feedrate_e": { "default_value": 120 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"machine_max_jerk_z": { "default_value": 0.2 },
"machine_max_jerk_e": { "default_value": 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": "G28 \nG1 Z15 F300\nM107\nG90\nM82\nM104 S215\nM140 S55\nG92 E0\nM109 S215\nM107\nM163 S0 P0.50\nM163 S1 P0.50\nM164 S4\nG0 X10 Y20 F6000\nG1 Z0.8\nG1 F300 X180 E40\nG1 F1200 Z2\nG92 E0\nG28"
},
"machine_end_gcode": {
"default_value": "G91\nG1 E-1\nG0 X0 Y200\nM104 S0\nG90\nG92 E0\nM140 S0\nM84\nM104 S0\nM140 S0\nM84"
},
"machine_extruder_count": {
"default_value": 2
}
}
}

View File

@ -0,0 +1,59 @@
{
"version": 2,
"name": "Geeetech A10T",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Amit L",
"manufacturer": "Geeetech",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains":
{
"0": "geeetech_A10T_1",
"1": "geeetech_A10T_2",
"2": "geeetech_A10T_3"
}
},
"overrides": {
"machine_name": { "default_value": "Geeetech A10T" },
"machine_width": {
"default_value": 220
},
"machine_height": {
"default_value": 220
},
"machine_depth": {
"default_value": 260
}, "machine_center_is_zero": {
"default_value": false
},
"layer_height": { "default_value": 0.1 },
"layer_height_0": { "default_value": 0.15 },
"retraction_amount": { "default_value": 0.8 },
"retraction_speed": { "default_value": 35 },
"adhesion_type": { "default_value": "skirt" },
"machine_head_with_fans_polygon": { "default_value": [[-31,31],[34,31],[34,-40],[-31,-40]] },
"gantry_height": { "value": "28" },
"machine_max_feedrate_z": { "default_value": 12 },
"machine_max_feedrate_e": { "default_value": 120 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"machine_max_jerk_z": { "default_value": 0.2 },
"machine_max_jerk_e": { "default_value": 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": "G28 \nG1 Z15 F300\nM107\nG90\nM82\nM104 S215\nM140 S55\nG92 E0\nM109 S215\nM107\nM163 S0 P0.33\nM163 S1 P0.33\nM163 S2 P0.33\nM164 S4\nG0 X10 Y20 F6000\nG1 Z0.8\nG1 F300 X180 E40\nG1 F1200 Z2\nG92 E0\nG28"
},
"machine_end_gcode": {
"default_value": "G91\nG1 E-1\nG0 X0 Y200\nM104 S0\nG90\nG92 E0\nM140 S0\nM84\nM104 S0\nM140 S0\nM84"
},
"machine_extruder_count": {
"default_value": 3
}
}
}

View File

@ -0,0 +1,57 @@
{
"version": 2,
"name": "Geeetech A20",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Amit L",
"manufacturer": "Geeetech",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains":
{
"0": "geeetech_A20_1"
}
},
"overrides": {
"machine_name": { "default_value": "Geeetech A20" },
"machine_width": {
"default_value": 250
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 250
}, "machine_center_is_zero": {
"default_value": false
},
"layer_height": { "default_value": 0.1 },
"layer_height_0": { "default_value": 0.15 },
"retraction_amount": { "default_value": 0.8 },
"retraction_speed": { "default_value": 35 },
"adhesion_type": { "default_value": "skirt" },
"machine_head_with_fans_polygon": { "default_value": [[-31,31],[34,31],[34,-40],[-31,-40]] },
"gantry_height": { "value": "28" },
"machine_max_feedrate_z": { "default_value": 12 },
"machine_max_feedrate_e": { "default_value": 120 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"machine_max_jerk_z": { "default_value": 0.2 },
"machine_max_jerk_e": { "default_value": 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": "G28 \nG1 Z15 F300\nM107\nG90\nM82\nM104 S215\nM140 S55\nG92 E0\nM109 S215\nM107\nG0 X10 Y20 F6000\nG1 Z0.8\nG1 F300 X200 E40\nG1 F1200 Z2\nG92 E0\nG28"
},
"machine_end_gcode": {
"default_value": "G91\nG1 E-1\nG0 X0 Y200\nM104 S0\nG90\nG92 E0\nM140 S0\nM84\nM104 S0\nM140 S0\nM84"
},
"machine_extruder_count": {
"default_value": 1
}
}
}

View File

@ -0,0 +1,58 @@
{
"version": 2,
"name": "Geeetech A20M",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Amit L",
"manufacturer": "Geeetech",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains":
{
"0": "geeetech_A20M_1",
"1": "geeetech_A20M_2"
}
},
"overrides": {
"machine_name": { "default_value": "Geeetech A20M" },
"machine_width": {
"default_value": 250
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 250
}, "machine_center_is_zero": {
"default_value": false
},
"layer_height": { "default_value": 0.1 },
"layer_height_0": { "default_value": 0.15 },
"retraction_amount": { "default_value": 0.8 },
"retraction_speed": { "default_value": 35 },
"adhesion_type": { "default_value": "skirt" },
"machine_head_with_fans_polygon": { "default_value": [[-31,31],[34,31],[34,-40],[-31,-40]] },
"gantry_height": { "value": "28" },
"machine_max_feedrate_z": { "default_value": 12 },
"machine_max_feedrate_e": { "default_value": 120 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"machine_max_jerk_z": { "default_value": 0.2 },
"machine_max_jerk_e": { "default_value": 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": "G28 \nG1 Z15 F300\nM107\nG90\nM82\nM104 S215\nM140 S55\nG92 E0\nM109 S215\nM107\nM163 S0 P0.50\nM163 S1 P0.50\nM164 S4\nG0 X10 Y20 F6000\nG1 Z0.8\nG1 F300 X200 E40\nG1 F1200 Z2\nG92 E0\nG28"
},
"machine_end_gcode": {
"default_value": "G91\nG1 E-1\nG0 X0 Y200\nM104 S0\nG90\nG92 E0\nM140 S0\nM84\nM104 S0\nM140 S0\nM84"
},
"machine_extruder_count": {
"default_value": 2
}
}
}

View File

@ -0,0 +1,59 @@
{
"version": 2,
"name": "Geeetech A20T",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Amit L",
"manufacturer": "Geeetech",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains":
{
"0": "geeetech_A20T_1",
"1": "geeetech_A20T_2",
"2": "geeetech_A20T_3"
}
},
"overrides": {
"machine_name": { "default_value": "Geeetech A20T" },
"machine_width": {
"default_value": 250
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 250
}, "machine_center_is_zero": {
"default_value": false
},
"layer_height": { "default_value": 0.1 },
"layer_height_0": { "default_value": 0.15 },
"retraction_amount": { "default_value": 0.8 },
"retraction_speed": { "default_value": 35 },
"adhesion_type": { "default_value": "skirt" },
"machine_head_with_fans_polygon": { "default_value": [[-31,31],[34,31],[34,-40],[-31,-40]] },
"gantry_height": { "value": "28" },
"machine_max_feedrate_z": { "default_value": 12 },
"machine_max_feedrate_e": { "default_value": 120 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"machine_max_jerk_z": { "default_value": 0.2 },
"machine_max_jerk_e": { "default_value": 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": "G28 \nG1 Z15 F300\nM107\nG90\nM82\nM104 S215\nM140 S55\nG92 E0\nM109 S215\nM107\nM163 S0 P0.33\nM163 S1 P0.33\nM163 S2 P0.33\nM164 S4\nG0 X10 Y20 F6000\nG1 Z0.8\nG1 F300 X200 E40\nG1 F1200 Z2\nG92 E0\nG28"
},
"machine_end_gcode": {
"default_value": "G91\nG1 E-1\nG0 X0 Y200\nM104 S0\nG90\nG92 E0\nM140 S0\nM84\nM104 S0\nM140 S0\nM84"
},
"machine_extruder_count": {
"default_value": 3
}
}
}

View File

@ -0,0 +1,96 @@
{
"version": 2,
"name": "MAKEiT Pro-MX",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "unknown",
"manufacturer": "MAKEiT 3D",
"file_formats": "text/x-gcode",
"has_materials": false,
"machine_extruder_trains":
{
"0": "makeit_mx_dual_1st",
"1": "makeit_mx_dual_2nd"
}
},
"overrides": {
"machine_name": { "default_value": "MAKEiT Pro-MX" },
"machine_width": {
"default_value": 200
},
"machine_height": {
"default_value": 330
},
"machine_depth": {
"default_value": 240
},
"machine_center_is_zero": {
"default_value": false
},
"machine_head_with_fans_polygon":
{
"default_value": [
[ -200, 240 ],
[ -200, -32 ],
[ 200, 240 ],
[ 200, -32 ]
]
},
"gantry_height": {
"value": "200"
},
"machine_use_extruder_offset_to_offset_coords": {
"default_value": true
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG92 E0 ;zero the extruded length\nG28 ;home\nG1 F200 E30 ;extrude 30 mm of feed stock\nG92 E0 ;zero the extruded length\nG1 E-5 ;retract 5 mm\nG28 SC ;Do homeing, clean nozzles and let printer to know that printing started\nG92 X-6 ;Sets Curas checker board to match printers heated bed coordinates\nG1 F{speed_travel}\nM117 Printing..."
},
"machine_end_gcode": {
"default_value": "M104 T0 S0 ;1st extruder heater off\nM104 T1 S0 ;2nd extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-5 F9000 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+5 X+20 Y+20 F9000 ;move Z up a bit\nM117 MAKEiT Pro@Done\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM81"
},
"machine_extruder_count": {
"default_value": 2
},
"print_sequence": {
"enabled": false
},
"prime_tower_position_x": {
"value": "185"
},
"prime_tower_position_y": {
"value": "160"
},
"layer_height": {
"default_value": 0.2
},
"retraction_speed": {
"default_value": 180
},
"infill_sparse_density": {
"default_value": 20
},
"retraction_amount": {
"default_value": 6
},
"speed_print": {
"default_value": 60
},
"wall_thickness": {
"default_value": 1.2
},
"cool_min_layer_time_fan_speed_max": {
"default_value": 5
},
"adhesion_type": {
"default_value": "skirt"
},
"machine_heated_bed": {
"default_value": true
}
}
}

View File

@ -0,0 +1,76 @@
{
"version": 2,
"name": "MP Mini Delta",
"inherits": "fdmprinter",
"metadata": {
"author": "MPMD Facebook Group",
"manufacturer": "Monoprice",
"category": "Other",
"file_formats": "text/x-gcode",
"platform": "mp_mini_delta_platform.stl",
"supports_usb_connection": true,
"has_machine_quality": false,
"visible": true,
"platform_offset": [0, 0, 0],
"has_materials": true,
"has_variants": false,
"has_machine_materials": false,
"has_variant_materials": false,
"preferred_quality_type": "normal",
"machine_extruder_trains":
{
"0": "mp_mini_delta_extruder_0"
}
},
"overrides": {
"machine_start_gcode":
{
"default_value": ";MPMD Basic Calibration Tutorial: \n; https://www.thingiverse.com/thing:3892011 \n; \n; If you want to put calibration values in your \n; Start Gcode, put them here. \n; \n;If on stock firmware, at minimum, consider adding \n;M665 R here since there is a firmware bug. \n; \n; Calibration part ends here \n; \nG90 ; switch to absolute positioning \nG92 E0 ; reset extrusion distance \nG1 E20 F200 ; purge 20mm of filament to prime nozzle. \nG92 E0 ; reset extrusion distance \nG4 S5 ; Pause for 5 seconds to allow time for removing extruded filament \nG28 ; start from home position \nG1 E-6 F900 ; retract 6mm of filament before starting the bed leveling process \nG92 E0 ; reset extrusion distance \nG4 S5 ; pause for 5 seconds to allow time for removing extruded filament \nG29 P2 Z0.28 ; Auto-level ; ADJUST Z higher or lower to set first layer height. Start with 0.02 adjustments. \nG1 Z30 ; raise Z 30mm to prepare for priming the nozzle \nG1 E5 F200 ; extrude 5mm of filament to help prime the nozzle just prior to the start of the print \nG92 E0 ; reset extrusion distance \nG4 S5 ; pause for 5 seconds to allow time for cleaning the nozzle and build plate if needed "
},
"machine_end_gcode":
{
"default_value": "M107; \nM104 S0; turn off hotend heater \nM140 S0; turn off bed heater \nG91; Switch to use Relative Coordinates \nG1 E-2 F300; retract the filament a bit before lifting the nozzle to release some of the pressure \nG1 Z5 E-5 F4800; move nozzle up a bit and retract filament even more \nG28 X0; return to home positions so the nozzle is out of the way \nM84; turn off stepper motors \nG90; switch to absolute positioning \nM82; absolute extrusion mode"
},
"machine_width": { "default_value": 110 },
"machine_depth": { "default_value": 110 },
"machine_height": { "default_value": 120 },
"machine_heated_bed": { "default_value": true },
"machine_shape": { "default_value": "elliptic" },
"machine_center_is_zero": { "default_value": true },
"machine_nozzle_size": {
"default_value": 0.4,
"minimum_value": 0.10,
"maximum_value": 0.80
},
"layer_height": {
"default_value": 0.14,
"minimum_value": 0.04
},
"layer_height_0": {
"default_value": 0.21,
"minimum_value": 0.07
},
"line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_bed_temperature_layer_0": { "value": "material_bed_temperature + 5" },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_max_feedrate_x": { "default_value": 150 },
"machine_max_feedrate_y": { "default_value": 150 },
"machine_max_feedrate_z": { "default_value": 150 },
"machine_max_feedrate_e": { "default_value": 50 },
"machine_max_acceleration_x": { "default_value": 800 },
"machine_max_acceleration_y": { "default_value": 800 },
"machine_max_acceleration_z": { "default_value": 800 },
"machine_max_acceleration_e": { "default_value": 10000 },
"machine_acceleration": { "default_value": 3000 },
"machine_max_jerk_xy": { "default_value": 20 },
"machine_max_jerk_z": { "default_value": 20 },
"machine_max_jerk_e": { "default_value": 5},
"retraction_amount": { "default_value": 4 },
"retraction_speed": { "default_value": 50 },
"retraction_hop_enabled": { "default_value": false },
"retract_at_layer_change": { "default_value": true },
"coasting_enable": { "default_value": true }
}
}

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,9 @@
"retraction_enable": {
"value": "True"
},
"retraction_hop_enabled": {
"value": "1.0"
},
"retraction_min_travel": {
"value": "1.5"
},
"retraction_speed": {
"value": "40.0"
},
"skin_overlap": {
"value": "15.0"
},
@ -185,6 +203,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 +213,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,228 @@
{
"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_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,216 @@
{
"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_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,19 @@
{
"version": 2,
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata": {
"machine": "geeetech_A10M",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "1"
},
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,27 @@
{
"version": 2,
"name": "1st Extruder",
"inherits": "fdmextruder",
"metadata": {
"machine": "makeit_pro_mx",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" }
}
}

View File

@ -0,0 +1,27 @@
{
"version": 2,
"name": "2nd Extruder",
"inherits": "fdmextruder",
"metadata": {
"machine": "makeit_pro_mx",
"position": "1"
},
"overrides": {
"extruder_nr": {
"default_value": 1,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" }
}
}

View File

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

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 }
}
}

Binary file not shown.

View File

@ -238,7 +238,7 @@ UM.MainWindow
if (filename.toLowerCase().endsWith(".curapackage"))
{
// Try to install plugin & close.
CuraApplication.getPackageManager().installPackageViaDragAndDrop(filename);
CuraApplication.installPackageViaDragAndDrop(filename);
packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting.");
packageInstallDialog.icon = StandardIcon.Information;
packageInstallDialog.open();

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;

View File

@ -1,36 +1,36 @@
// 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
//
// Cura-style TextArea with scrolls
//
ScrollView
{
property alias textArea: _textArea
clip: true
background: Rectangle // Border
{
color: UM.Theme.getColor("main_background")
border.color: UM.Theme.getColor("lining")
border.width: UM.Theme.getSize("default_lining").width
}
TextArea
{
id: _textArea
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
textFormat: TextEdit.PlainText
renderType: Text.NativeRendering
selectByMouse: true
}
}
// 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
//
// Cura-style TextArea with scrolls
//
ScrollView
{
property alias textArea: _textArea
clip: true
background: Rectangle // Border
{
color: UM.Theme.getColor("main_background")
border.color: UM.Theme.getColor("thick_lining")
border.width: UM.Theme.getSize("default_lining").width
}
TextArea
{
id: _textArea
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
textFormat: TextEdit.PlainText
renderType: Text.NativeRendering
selectByMouse: true
}
}

View File

@ -388,6 +388,7 @@ support_conical_enabled
support_conical_angle
support_conical_min_width
magic_fuzzy_skin_enabled
magic_fuzzy_skin_outside_only
magic_fuzzy_skin_thickness
magic_fuzzy_skin_point_density
magic_fuzzy_skin_point_dist

View File

@ -8,7 +8,7 @@
"main_background": [39, 44, 48, 255],
"message_background": [39, 44, 48, 255],
"wide_lining": [31, 36, 39, 255],
"thick_lining": [255, 255, 255, 30],
"thick_lining": [255, 255, 255, 60],
"lining": [64, 69, 72, 255],
"viewport_overlay": [30, 36, 39, 255],

View File

@ -149,7 +149,7 @@
"main_background": [255, 255, 255, 255],
"wide_lining": [245, 245, 245, 255],
"thick_lining": [127, 127, 127, 255],
"thick_lining": [180, 180, 180, 255],
"lining": [192, 193, 194, 255],
"viewport_overlay": [246, 246, 246, 255],
@ -520,6 +520,7 @@
"action_button": [15.0, 2.5],
"action_button_icon": [1.0, 1.0],
"action_button_radius": [0.15, 0.15],
"dialog_primary_button_padding": [3.0, 0],
"radio_button": [1.3, 1.3],

View File

@ -0,0 +1,65 @@
import os
import re
import sys
from pathlib import Path
"""
Run this file with the Cura project root as the working directory
Checks for invalid imports. When importing from plugins, there will be no problems when running from source,
but for some build types the plugins dir is not on the path, so relative imports should be used instead. eg:
from ..UltimakerCloudScope import UltimakerCloudScope <-- OK
import plugins.Toolbox.src ... <-- NOT OK
"""
class InvalidImportsChecker:
# compile regex
REGEX = re.compile(r"^\s*(from plugins|import plugins)")
def check(self):
""" Checks for invalid imports
:return: True if checks passed, False when the test fails
"""
cwd = os.getcwd()
cura_result = checker.check_dir(os.path.join(cwd, "cura"))
plugins_result = checker.check_dir(os.path.join(cwd, "plugins"))
result = cura_result and plugins_result
if not result:
print("error: sources contain invalid imports. Use relative imports when referencing plugin source files")
return result
def check_dir(self, root_dir: str) -> bool:
""" Checks a directory for invalid imports
:return: True if checks passed, False when the test fails
"""
passed = True
for path_like in Path(root_dir).rglob('*.py'):
if not self.check_file(str(path_like)):
passed = False
return passed
def check_file(self, file_path):
""" Checks a file for invalid imports
:return: True if checks passed, False when the test fails
"""
passed = True
with open(file_path, 'r', encoding = "utf-8") as inputFile:
# loop through each line in file
for line_i, line in enumerate(inputFile, 1):
# check if we have a regex match
match = self.REGEX.search(line)
if match:
path = os.path.relpath(file_path)
print("{path}:{line_i}:{match}".format(path=path, line_i=line_i, match=match.group(1)))
passed = False
return passed
if __name__ == "__main__":
checker = InvalidImportsChecker()
sys.exit(0 if checker.check() else 1)