mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-12 22:58:58 +08:00
commit
ca3cce9252
12
.github/workflows/cicd.yml
vendored
12
.github/workflows/cicd.yml
vendored
@ -1,13 +1,19 @@
|
||||
---
|
||||
name: CI/CD
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- 'WIP**'
|
||||
- '4.*'
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
container: ultimaker/cura-build-environment
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v1.2.0
|
||||
- name: Checkout Cura
|
||||
uses: actions/checkout@v2
|
||||
- name: Build and test
|
||||
run: docker/build.sh
|
||||
|
116
.pylintrc
Normal file
116
.pylintrc
Normal file
@ -0,0 +1,116 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# This file contains the Pylint rules used in the stardust projects.
|
||||
|
||||
# To configure PyLint as an external tool in PyCharm, create a new External Tool with the settings:
|
||||
#
|
||||
# Name: PyLint
|
||||
# Program: Check with 'which pylint'. For example: ~/.local/bin/pylint
|
||||
# Arguments: $FileDirName$ --rcfile=.pylintrc --msg-template='{abspath}:{line}:{column}:({symbol}):{msg_id}:{msg}'
|
||||
# Working directory: $ContentRoot$
|
||||
# Output filters: $FILE_PATH$:$LINE$:$COLUMN$:.*
|
||||
#
|
||||
# You can add a keyboard shortcut in the keymap settings. To run Pylint to a project, select the module
|
||||
# you want to check (e.g. cura folder) before running the external tool.
|
||||
#
|
||||
# If you find a better way to configure the external tool please edit this file.
|
||||
|
||||
[MASTER]
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=pylint_quotes
|
||||
|
||||
# We expect double string quotes
|
||||
string-quote=double-avoid-escape
|
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||
# user-friendly hints instead of false-positive error messages.
|
||||
suggestion-mode=yes
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not paths.
|
||||
ignore=tests
|
||||
|
||||
[REFACTORING]
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
# C0326: No space allowed around keyword argument assignment
|
||||
# C0411: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds
|
||||
# C0412: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds
|
||||
# C0413: Ignore import order because the rules are different than in PyCharm, so automatic imports break lots of builds
|
||||
# R0201: Method could be a function (no-self-use)
|
||||
# R0401: Cyclic imports (cyclic-import) are used for typing
|
||||
# R0801: Unfortunately the error is triggered for a lot of similar models (duplicate-code)
|
||||
# R1710: Either all return statements in a function should return an expression, or none of them should.
|
||||
# W0221: Parameters differ from overridden method (tornado http methods have a flexible number of parameters)
|
||||
# W0511: Ignore warnings generated for TODOs in the code
|
||||
# C0111: We don't use docstring
|
||||
# C0303: Trailing whitespace isn't something we care about
|
||||
# C4001: You can put " in a string if you escape it first...
|
||||
disable=C0326,C0411,C0412,C0413,R0201,R0401,R0801,R1710,W0221,W0511, C0111, C0303,C4001
|
||||
|
||||
[FORMAT]
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=120
|
||||
|
||||
# Maximum number of lines in a module.
|
||||
max-module-lines=500
|
||||
|
||||
good-names=os
|
||||
|
||||
[BASIC]
|
||||
# allow modules and functions to use PascalCase
|
||||
module-rgx=[a-zA-Z0-9_]+$
|
||||
function-rgx=
|
||||
## Allowed methods:
|
||||
# getSomething
|
||||
# _getSomething
|
||||
# __getSomething
|
||||
# __new__
|
||||
## Disallowed:
|
||||
# _GET
|
||||
# GetSomething
|
||||
method-rgx=(_{,2}[a-z][A-Za-z0-9]*_{,2})$
|
||||
|
||||
[DESIGN]
|
||||
# Maximum number of arguments for function / method.
|
||||
max-args=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=8
|
||||
|
||||
# Maximum number of boolean expressions in an if statement.
|
||||
max-bool-expr=5
|
||||
|
||||
# Maximum number of branch for function / method body.
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of locals for function / method body.
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of return / yield for function / method body.
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of statements in function / method body.
|
||||
max-statements=50
|
||||
|
||||
# Minimum number of public methods for a class (R0903).
|
||||
# We set this to 0 because our models and fields do not have methods.
|
||||
min-public-methods=0
|
||||
|
||||
ignored-argument-names=arg|args|kwargs|_
|
||||
|
||||
[CLASSES]
|
||||
defining-attr-methods=__init__,__new__,setUp,initialize
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-classes=NotImplemented
|
||||
|
||||
[VARIABLES]
|
||||
dummy-variables-rgx=_+[a-z0-9_]{2,30}
|
@ -1,6 +1,6 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Logger import Logger
|
||||
@ -8,6 +8,7 @@ from UM.Math.Polygon import Polygon
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
from cura.BuildVolume import BuildVolume
|
||||
from cura.Scene import ZOffsetDecorator
|
||||
|
||||
from collections import namedtuple
|
||||
@ -27,7 +28,7 @@ LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points
|
||||
#
|
||||
# Note: Make sure the scale is the same between ShapeArray objects and the Arrange instance.
|
||||
class Arrange:
|
||||
build_volume = None
|
||||
build_volume = None # type: Optional[BuildVolume]
|
||||
|
||||
def __init__(self, x, y, offset_x, offset_y, scale= 0.5):
|
||||
self._scale = scale # convert input coordinates to arrange coordinates
|
||||
@ -68,7 +69,7 @@ class Arrange:
|
||||
points = copy.deepcopy(vertices._points)
|
||||
|
||||
# After scaling (like up to 0.1 mm) the node might not have points
|
||||
if len(points) == 0:
|
||||
if not points:
|
||||
continue
|
||||
|
||||
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
|
||||
@ -113,7 +114,7 @@ class Arrange:
|
||||
found_spot = True
|
||||
self.place(x, y, offset_shape_arr) # place the object in arranger
|
||||
else:
|
||||
Logger.log("d", "Could not find spot!"),
|
||||
Logger.log("d", "Could not find spot!")
|
||||
found_spot = False
|
||||
node.setPosition(Vector(200, center_y, 100))
|
||||
return found_spot
|
||||
|
@ -29,7 +29,7 @@ class ArrangeArray:
|
||||
self._has_empty = False
|
||||
self._arrange = [] # type: List[Arrange]
|
||||
|
||||
def _update_first_empty(self):
|
||||
def _updateFirstEmpty(self):
|
||||
for i, a in enumerate(self._arrange):
|
||||
if a.isEmpty:
|
||||
self._first_empty = i
|
||||
@ -42,7 +42,7 @@ class ArrangeArray:
|
||||
new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes)
|
||||
self._arrange.append(new_arrange)
|
||||
self._count += 1
|
||||
self._update_first_empty()
|
||||
self._updateFirstEmpty()
|
||||
|
||||
def count(self):
|
||||
return self._count
|
||||
|
@ -2,12 +2,16 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
class AutoSave:
|
||||
def __init__(self, application):
|
||||
def __init__(self, application: "CuraApplication") -> None:
|
||||
self._application = application
|
||||
self._application.getPreferences().preferenceChanged.connect(self._triggerTimer)
|
||||
|
||||
@ -22,14 +26,14 @@ class AutoSave:
|
||||
self._enabled = True
|
||||
self._saving = False
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
# only initialise if the application is created and has started
|
||||
self._change_timer.timeout.connect(self._onTimeout)
|
||||
self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
self._onGlobalStackChanged()
|
||||
self._triggerTimer()
|
||||
|
||||
def _triggerTimer(self, *args):
|
||||
def _triggerTimer(self, *args: Any) -> None:
|
||||
if not self._saving:
|
||||
self._change_timer.start()
|
||||
|
||||
@ -40,7 +44,7 @@ class AutoSave:
|
||||
else:
|
||||
self._change_timer.stop()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
def _onGlobalStackChanged(self) -> None:
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.disconnect(self._triggerTimer)
|
||||
self._global_stack.containersChanged.disconnect(self._triggerTimer)
|
||||
@ -51,7 +55,7 @@ class AutoSave:
|
||||
self._global_stack.propertyChanged.connect(self._triggerTimer)
|
||||
self._global_stack.containersChanged.connect(self._triggerTimer)
|
||||
|
||||
def _onTimeout(self):
|
||||
def _onTimeout(self) -> None:
|
||||
self._saving = True # To prevent the save process from triggering another autosave.
|
||||
Logger.log("d", "Autosaving preferences, instances and profiles")
|
||||
|
||||
|
@ -1,15 +1,21 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import numpy
|
||||
import math
|
||||
|
||||
from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict
|
||||
|
||||
from UM.Mesh.MeshData import MeshData
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
|
||||
from UM.Application import Application #To modify the maximum zoom level.
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Scene.Platform import Platform
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Resources import Resources
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Math.Matrix import Matrix
|
||||
from UM.Math.Color import Color
|
||||
@ -17,23 +23,23 @@ from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Polygon import Polygon
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal
|
||||
from PyQt5.QtCore import QTimer
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
import numpy
|
||||
import math
|
||||
|
||||
from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
# Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position.
|
||||
PRIME_CLEARANCE = 6.5
|
||||
|
||||
@ -1012,13 +1018,13 @@ class BuildVolume(SceneNode):
|
||||
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
|
||||
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
|
||||
for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
|
||||
if not setting_value and (setting_type == "int" or setting_type == "float"):
|
||||
if not setting_value and setting_type in ["int", "float"]:
|
||||
all_values[i] = 0
|
||||
return all_values
|
||||
|
||||
def _calculateBedAdhesionSize(self, used_extruders):
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
return None
|
||||
|
||||
container_stack = self._global_container_stack
|
||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import platform
|
||||
@ -7,13 +7,18 @@ import faulthandler
|
||||
import tempfile
|
||||
import os
|
||||
import os.path
|
||||
import time
|
||||
import uuid
|
||||
import json
|
||||
import ssl
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import locale
|
||||
from typing import cast
|
||||
|
||||
import certifi
|
||||
try:
|
||||
from sentry_sdk.hub import Hub
|
||||
from sentry_sdk.utils import event_from_exception
|
||||
from sentry_sdk import configure_scope
|
||||
with_sentry_sdk = True
|
||||
except ImportError:
|
||||
with_sentry_sdk = False
|
||||
|
||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QUrl
|
||||
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
||||
@ -24,7 +29,6 @@ from UM.Logger import Logger
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Resources import Resources
|
||||
|
||||
from cura import ApplicationMetadata
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
@ -46,31 +50,32 @@ skip_exception_types = [
|
||||
GeneratorExit
|
||||
]
|
||||
|
||||
class CrashHandler:
|
||||
crash_url = "https://stats.ultimaker.com/api/cura"
|
||||
|
||||
class CrashHandler:
|
||||
def __init__(self, exception_type, value, tb, has_started = True):
|
||||
self.exception_type = exception_type
|
||||
self.value = value
|
||||
self.traceback = tb
|
||||
self.has_started = has_started
|
||||
self.dialog = None # Don't create a QDialog before there is a QApplication
|
||||
|
||||
# While we create the GUI, the information will be stored for sending afterwards
|
||||
self.data = dict()
|
||||
self.data["time_stamp"] = time.time()
|
||||
self.cura_version = None
|
||||
self.cura_locale = None
|
||||
|
||||
Logger.log("c", "An uncaught error has occurred!")
|
||||
for line in traceback.format_exception(exception_type, value, tb):
|
||||
for part in line.rstrip("\n").split("\n"):
|
||||
Logger.log("c", part)
|
||||
|
||||
self.data = {}
|
||||
# If Cura has fully started, we only show fatal errors.
|
||||
# If Cura has not fully started yet, we always show the early crash dialog. Otherwise, Cura will just crash
|
||||
# without any information.
|
||||
if has_started and exception_type in skip_exception_types:
|
||||
return
|
||||
|
||||
if with_sentry_sdk:
|
||||
with configure_scope() as scope:
|
||||
scope.set_tag("during_startup", not has_started)
|
||||
|
||||
if not has_started:
|
||||
self._send_report_checkbox = None
|
||||
self.early_crash_dialog = self._createEarlyCrashDialog()
|
||||
@ -179,25 +184,43 @@ class CrashHandler:
|
||||
try:
|
||||
from UM.Application import Application
|
||||
self.cura_version = Application.getInstance().getVersion()
|
||||
self.cura_locale = Application.getInstance().getPreferences().getValue("general/language")
|
||||
except:
|
||||
self.cura_version = catalog.i18nc("@label unknown version of Cura", "Unknown")
|
||||
self.cura_locale = "??_??"
|
||||
|
||||
self.data["cura_version"] = self.cura_version
|
||||
self.data["os"] = {"type": platform.system(), "version": platform.version()}
|
||||
self.data["qt_version"] = QT_VERSION_STR
|
||||
self.data["pyqt_version"] = PYQT_VERSION_STR
|
||||
self.data["locale_os"] = locale.getlocale(locale.LC_MESSAGES)[0] if hasattr(locale, "LC_MESSAGES") else \
|
||||
locale.getdefaultlocale()[0]
|
||||
self.data["locale_cura"] = self.cura_locale
|
||||
|
||||
crash_info = "<b>" + catalog.i18nc("@label Cura version number", "Cura version") + ":</b> " + str(self.cura_version) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label Cura build type", "Cura build type") + ":</b> " + str(ApplicationMetadata.CuraBuildType) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label", "Cura language") + ":</b> " + str(self.cura_locale) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label", "OS language") + ":</b> " + str(self.data["locale_os"]) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label Type of platform", "Platform") + ":</b> " + str(platform.platform()) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label", "Qt version") + ":</b> " + str(QT_VERSION_STR) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label", "PyQt version") + ":</b> " + str(PYQT_VERSION_STR) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label OpenGL version", "OpenGL") + ":</b> " + str(self._getOpenGLInfo()) + "<br/>"
|
||||
|
||||
label.setText(crash_info)
|
||||
|
||||
layout.addWidget(label)
|
||||
group.setLayout(layout)
|
||||
|
||||
self.data["cura_version"] = self.cura_version
|
||||
self.data["cura_build_type"] = ApplicationMetadata.CuraBuildType
|
||||
self.data["os"] = {"type": platform.system(), "version": platform.version()}
|
||||
self.data["qt_version"] = QT_VERSION_STR
|
||||
self.data["pyqt_version"] = PYQT_VERSION_STR
|
||||
if with_sentry_sdk:
|
||||
with configure_scope() as scope:
|
||||
scope.set_tag("qt_version", QT_VERSION_STR)
|
||||
scope.set_tag("pyqt_version", PYQT_VERSION_STR)
|
||||
scope.set_tag("os", platform.system())
|
||||
scope.set_tag("os_version", platform.version())
|
||||
scope.set_tag("locale_os", self.data["locale_os"])
|
||||
scope.set_tag("locale_cura", self.cura_locale)
|
||||
scope.set_tag("is_enterprise", ApplicationMetadata.IsEnterpriseVersion)
|
||||
|
||||
scope.set_user({"id": str(uuid.getnode())})
|
||||
|
||||
return group
|
||||
|
||||
@ -215,6 +238,31 @@ class CrashHandler:
|
||||
|
||||
self.data["opengl"] = {"version": opengl_instance.getOpenGLVersion(), "vendor": opengl_instance.getGPUVendorName(), "type": opengl_instance.getGPUType()}
|
||||
|
||||
active_machine_definition_id = "unknown"
|
||||
active_machine_manufacturer = "unknown"
|
||||
|
||||
try:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = cast(CuraApplication, Application.getInstance())
|
||||
machine_manager = application.getMachineManager()
|
||||
global_stack = machine_manager.activeMachine
|
||||
if global_stack is None:
|
||||
active_machine_definition_id = "empty"
|
||||
active_machine_manufacturer = "empty"
|
||||
else:
|
||||
active_machine_definition_id = global_stack.definition.getId()
|
||||
active_machine_manufacturer = global_stack.definition.getMetaDataEntry("manufacturer", "unknown")
|
||||
except:
|
||||
pass
|
||||
|
||||
if with_sentry_sdk:
|
||||
with configure_scope() as scope:
|
||||
scope.set_tag("opengl_version", opengl_instance.getOpenGLVersion())
|
||||
scope.set_tag("gpu_vendor", opengl_instance.getGPUVendorName())
|
||||
scope.set_tag("gpu_type", opengl_instance.getGPUType())
|
||||
scope.set_tag("active_machine", active_machine_definition_id)
|
||||
scope.set_tag("active_machine_manufacturer", active_machine_manufacturer)
|
||||
|
||||
return info
|
||||
|
||||
def _exceptionInfoWidget(self):
|
||||
@ -296,6 +344,11 @@ class CrashHandler:
|
||||
"module_name": module_name, "version": module_version, "is_plugin": isPlugin}
|
||||
self.data["exception"] = exception_dict
|
||||
|
||||
if with_sentry_sdk:
|
||||
with configure_scope() as scope:
|
||||
scope.set_tag("is_plugin", isPlugin)
|
||||
scope.set_tag("module", module_name)
|
||||
|
||||
return group
|
||||
|
||||
def _logInfoWidget(self):
|
||||
@ -353,35 +406,24 @@ class CrashHandler:
|
||||
# Before sending data, the user comments are stored
|
||||
self.data["user_info"] = self.user_description_text_area.toPlainText()
|
||||
|
||||
# Convert data to bytes
|
||||
binary_data = json.dumps(self.data).encode("utf-8")
|
||||
|
||||
# CURA-6698 Create an SSL context and use certifi CA certificates for verification.
|
||||
context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
|
||||
context.load_verify_locations(cafile = certifi.where())
|
||||
# Submit data
|
||||
kwoptions = {"data": binary_data,
|
||||
"timeout": 5,
|
||||
"context": context}
|
||||
|
||||
Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)
|
||||
if not self.has_started:
|
||||
print("Sending crash report info to [%s]...\n" % self.crash_url)
|
||||
|
||||
try:
|
||||
f = urllib.request.urlopen(self.crash_url, **kwoptions)
|
||||
Logger.log("i", "Sent crash report info.")
|
||||
if with_sentry_sdk:
|
||||
try:
|
||||
hub = Hub.current
|
||||
event, hint = event_from_exception((self.exception_type, self.value, self.traceback))
|
||||
hub.capture_event(event, hint=hint)
|
||||
hub.flush()
|
||||
except Exception as e: # We don't want any exception to cause problems
|
||||
Logger.logException("e", "An exception occurred while trying to send crash report")
|
||||
if not self.has_started:
|
||||
print("An exception occurred while trying to send crash report: %s" % e)
|
||||
else:
|
||||
msg = "SentrySDK is not available and the report could not be sent."
|
||||
Logger.logException("e", msg)
|
||||
if not self.has_started:
|
||||
print("Sent crash report info.\n")
|
||||
f.close()
|
||||
except urllib.error.HTTPError as e:
|
||||
Logger.logException("e", "An HTTP error occurred while trying to send crash report")
|
||||
if not self.has_started:
|
||||
print("An HTTP error occurred while trying to send crash report: %s" % e)
|
||||
except Exception as e: # We don't want any exception to cause problems
|
||||
Logger.logException("e", "An exception occurred while trying to send crash report")
|
||||
if not self.has_started:
|
||||
print("An exception occurred while trying to send crash report: %s" % e)
|
||||
print(msg)
|
||||
print("Exception type: {}".format(self.exception_type))
|
||||
print("Value: {}".format(self.value))
|
||||
print("Traceback: {}".format(self.traceback))
|
||||
|
||||
os._exit(1)
|
||||
|
||||
|
@ -3,17 +3,15 @@
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from typing import List, Optional, cast
|
||||
from typing import List, cast
|
||||
|
||||
from UM.Event import CallFunctionEvent
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Math.Quaternion import Quaternion
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||
from UM.Operations.RotateOperation import RotateOperation
|
||||
from UM.Operations.TranslateOperation import TranslateOperation
|
||||
|
||||
import cura.CuraApplication
|
||||
|
@ -4,7 +4,7 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List, Any
|
||||
|
||||
import numpy
|
||||
|
||||
@ -15,7 +15,7 @@ from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qm
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Application import Application
|
||||
from UM.Decorators import override, deprecated
|
||||
from UM.Decorators import override
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
@ -130,6 +130,8 @@ from . import CameraAnimation
|
||||
from . import CuraActions
|
||||
from . import PrintJobPreviewImageProvider
|
||||
|
||||
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
||||
|
||||
from cura import ApplicationMetadata, UltimakerCloudAuthentication
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
@ -191,7 +193,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
self._cura_package_manager = None
|
||||
|
||||
self._machine_action_manager = None
|
||||
self._machine_action_manager = None # type: Optional[MachineActionManager.MachineActionManager]
|
||||
|
||||
self.empty_container = None # type: EmptyInstanceContainer
|
||||
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
||||
@ -264,7 +266,6 @@ class CuraApplication(QtApplication):
|
||||
# Backups
|
||||
self._auto_save = None # type: Optional[AutoSave]
|
||||
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
self._container_registry_class = CuraContainerRegistry
|
||||
# Redefined here in order to please the typing.
|
||||
self._container_registry = None # type: CuraContainerRegistry
|
||||
@ -508,14 +509,14 @@ class CuraApplication(QtApplication):
|
||||
self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
|
||||
self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed)
|
||||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))
|
||||
|
||||
self._container_registry.allMetadataLoaded.connect(ContainerRegistry.getInstance)
|
||||
|
||||
with self._container_registry.lockFile():
|
||||
self._container_registry.loadAllMetadata()
|
||||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up preferences..."))
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Setting up preferences..."))
|
||||
# Set the setting version for Preferences
|
||||
preferences = self.getPreferences()
|
||||
preferences.addPreference("metadata/setting_version", 0)
|
||||
@ -637,6 +638,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
@override(Application)
|
||||
def setGlobalContainerStack(self, stack: "GlobalStack") -> None:
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine..."))
|
||||
super().setGlobalContainerStack(stack)
|
||||
|
||||
## A reusable dialogbox
|
||||
@ -696,7 +698,7 @@ class CuraApplication(QtApplication):
|
||||
self._message_box_callback_arguments = []
|
||||
|
||||
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
|
||||
def saveSettings(self):
|
||||
def saveSettings(self) -> None:
|
||||
if not self.started:
|
||||
# Do not do saving during application start or when data should not be saved on quit.
|
||||
return
|
||||
@ -741,18 +743,30 @@ class CuraApplication(QtApplication):
|
||||
|
||||
self._plugins_loaded = True
|
||||
|
||||
## Set a short, user-friendly hint about current loading status.
|
||||
# The way this message is displayed depends on application state
|
||||
def _setLoadingHint(self, hint: str):
|
||||
if self.started:
|
||||
Logger.info(hint)
|
||||
else:
|
||||
self.showSplashMessage(hint)
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
|
||||
Logger.log("i", "Initializing machine manager")
|
||||
self._machine_manager = MachineManager(self, parent = self)
|
||||
|
||||
Logger.log("i", "Initializing container manager")
|
||||
self._container_manager = ContainerManager(self)
|
||||
|
||||
Logger.log("i", "Initializing machine error checker")
|
||||
self._machine_error_checker = MachineErrorChecker(self)
|
||||
self._machine_error_checker.initialize()
|
||||
self.processEvents()
|
||||
|
||||
Logger.log("i", "Initializing machine manager")
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing machine manager..."))
|
||||
self.getMachineManager()
|
||||
self.processEvents()
|
||||
|
||||
Logger.log("i", "Initializing container manager")
|
||||
self._container_manager = ContainerManager(self)
|
||||
self.processEvents()
|
||||
|
||||
# Check if we should run as single instance or not. If so, set up a local socket server which listener which
|
||||
# coordinates multiple Cura instances and accepts commands.
|
||||
@ -760,6 +774,7 @@ class CuraApplication(QtApplication):
|
||||
self.__setUpSingleInstanceServer()
|
||||
|
||||
# Setup scene and build volume
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing build volume..."))
|
||||
root = self.getController().getScene().getRoot()
|
||||
self._volume = BuildVolume.BuildVolume(self, root)
|
||||
Arrange.build_volume = self._volume
|
||||
@ -767,13 +782,13 @@ class CuraApplication(QtApplication):
|
||||
# initialize info objects
|
||||
self._print_information = PrintInformation.PrintInformation(self)
|
||||
self._cura_actions = CuraActions.CuraActions(self)
|
||||
|
||||
self.processEvents()
|
||||
# Initialize setting visibility presets model.
|
||||
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self.getPreferences(), parent = self)
|
||||
|
||||
# Initialize Cura API
|
||||
self._cura_API.initialize()
|
||||
|
||||
self.processEvents()
|
||||
self._output_device_manager.start()
|
||||
self._welcome_pages_model.initialize()
|
||||
self._add_printer_pages_model.initialize()
|
||||
@ -821,7 +836,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
## Run Cura with GUI (desktop mode).
|
||||
def runWithGUI(self):
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
|
||||
|
||||
controller = self.getController()
|
||||
|
||||
@ -833,7 +848,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
# Set default background color for scene
|
||||
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
|
||||
|
||||
self.processEvents()
|
||||
# Initialize platform physics
|
||||
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
|
||||
|
||||
@ -856,11 +871,12 @@ class CuraApplication(QtApplication):
|
||||
self._camera_animation = CameraAnimation.CameraAnimation()
|
||||
self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
|
||||
|
||||
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
|
||||
|
||||
# Initialize QML engine
|
||||
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
||||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing engine..."))
|
||||
self.initializeEngine()
|
||||
|
||||
# Initialize UI state
|
||||
@ -918,7 +934,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
def getMachineManager(self, *args) -> MachineManager:
|
||||
if self._machine_manager is None:
|
||||
self._machine_manager = MachineManager(self)
|
||||
self._machine_manager = MachineManager(self, parent = self)
|
||||
return self._machine_manager
|
||||
|
||||
def getExtruderManager(self, *args) -> ExtruderManager:
|
||||
@ -972,8 +988,8 @@ class CuraApplication(QtApplication):
|
||||
## Get the machine action manager
|
||||
# We ignore any *args given to this, as we also register the machine manager as qml singleton.
|
||||
# It wants to give this function an engine and script engine, but we don't care about that.
|
||||
def getMachineActionManager(self, *args):
|
||||
return self._machine_action_manager
|
||||
def getMachineActionManager(self, *args: Any) -> MachineActionManager.MachineActionManager:
|
||||
return cast(MachineActionManager.MachineActionManager, self._machine_action_manager)
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getMaterialManagementModel(self) -> MaterialManagementModel:
|
||||
@ -1029,14 +1045,17 @@ class CuraApplication(QtApplication):
|
||||
super().registerObjects(engine)
|
||||
|
||||
# global contexts
|
||||
self.processEvents()
|
||||
engine.rootContext().setContextProperty("Printer", self)
|
||||
engine.rootContext().setContextProperty("CuraApplication", self)
|
||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
||||
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
|
||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||
@ -1045,16 +1064,16 @@ class CuraApplication(QtApplication):
|
||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
|
||||
|
||||
qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel")
|
||||
qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel")
|
||||
qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel")
|
||||
qmlRegisterType(TextManager, "Cura", 1, 0, "TextManager")
|
||||
qmlRegisterType(RecommendedMode, "Cura", 1, 0, "RecommendedMode")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage")
|
||||
|
||||
qmlRegisterType(ObjectsModel, "Cura", 1, 0, "ObjectsModel")
|
||||
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
|
||||
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
||||
@ -1062,14 +1081,15 @@ class CuraApplication(QtApplication):
|
||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
|
||||
qmlRegisterSingletonType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel", self.getQualityManagementModel)
|
||||
qmlRegisterSingletonType(MaterialManagementModel, "Cura", 1, 5, "MaterialManagementModel", self.getMaterialManagementModel)
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(DiscoveredPrintersModel, "Cura", 1, 0, "DiscoveredPrintersModel")
|
||||
|
||||
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
||||
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||
@ -1078,6 +1098,7 @@ class CuraApplication(QtApplication):
|
||||
qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel")
|
||||
qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel")
|
||||
|
||||
self.processEvents()
|
||||
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||
qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")
|
||||
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
||||
@ -1106,6 +1127,7 @@ class CuraApplication(QtApplication):
|
||||
continue
|
||||
|
||||
qmlRegisterType(QUrl.fromLocalFile(path), "Cura", 1, 0, type_name)
|
||||
self.processEvents()
|
||||
|
||||
def onSelectionChanged(self):
|
||||
if Selection.hasSelection():
|
||||
@ -1420,7 +1442,7 @@ class CuraApplication(QtApplication):
|
||||
if center is not None:
|
||||
object_centers.append(center)
|
||||
|
||||
if object_centers and len(object_centers) > 0:
|
||||
if object_centers:
|
||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
||||
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
||||
@ -1470,7 +1492,7 @@ class CuraApplication(QtApplication):
|
||||
if center is not None:
|
||||
object_centers.append(center)
|
||||
|
||||
if object_centers and len(object_centers) > 0:
|
||||
if object_centers:
|
||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
||||
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
||||
@ -1652,7 +1674,7 @@ class CuraApplication(QtApplication):
|
||||
extension = os.path.splitext(f)[1]
|
||||
extension = extension.lower()
|
||||
filename = os.path.basename(f)
|
||||
if len(self._currently_loading_files) > 0:
|
||||
if self._currently_loading_files:
|
||||
# If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files
|
||||
if extension in self._non_sliceable_extensions:
|
||||
message = Message(
|
||||
@ -1773,8 +1795,8 @@ class CuraApplication(QtApplication):
|
||||
node.addDecorator(build_plate_decorator)
|
||||
build_plate_decorator.setBuildPlateNumber(target_build_plate)
|
||||
|
||||
op = AddSceneNodeOperation(node, scene.getRoot())
|
||||
op.push()
|
||||
operation = AddSceneNodeOperation(node, scene.getRoot())
|
||||
operation.push()
|
||||
|
||||
node.callDecoration("setActiveExtruder", default_extruder_id)
|
||||
scene.sceneChanged.emit(node)
|
||||
@ -1848,16 +1870,14 @@ class CuraApplication(QtApplication):
|
||||
main_window = QtApplication.getInstance().getMainWindow()
|
||||
if main_window:
|
||||
return main_window.width()
|
||||
else:
|
||||
return 0
|
||||
return 0
|
||||
|
||||
@pyqtSlot(result = int)
|
||||
def appHeight(self) -> int:
|
||||
main_window = QtApplication.getInstance().getMainWindow()
|
||||
if main_window:
|
||||
return main_window.height()
|
||||
else:
|
||||
return 0
|
||||
return 0
|
||||
|
||||
@pyqtSlot()
|
||||
def deleteAll(self, only_selectable: bool = True) -> None:
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, TYPE_CHECKING, Optional
|
||||
|
||||
from cura.CuraApplication import CuraApplication #To find some resource types.
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
@ -9,12 +9,16 @@ from cura.Settings.GlobalStack import GlobalStack
|
||||
from UM.PackageManager import PackageManager #The class we're extending.
|
||||
from UM.Resources import Resources #To find storage paths for some resource types.
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from PyQt5.QtCore import QObject
|
||||
|
||||
|
||||
class CuraPackageManager(PackageManager):
|
||||
def __init__(self, application, parent = None):
|
||||
def __init__(self, application: "QtApplication", parent: Optional["QObject"] = None):
|
||||
super().__init__(application, parent)
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self._installation_dirs_dict["materials"] = Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer)
|
||||
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||
|
||||
|
@ -26,6 +26,7 @@ class CuraView(View):
|
||||
def mainComponent(self) -> QUrl:
|
||||
return self.getDisplayComponent("main")
|
||||
|
||||
|
||||
@pyqtProperty(QUrl, constant = True)
|
||||
def stageMenuComponent(self) -> QUrl:
|
||||
url = self.getDisplayComponent("menu")
|
||||
|
@ -33,10 +33,10 @@ class Layer:
|
||||
def elementCount(self):
|
||||
return self._element_count
|
||||
|
||||
def setHeight(self, height):
|
||||
def setHeight(self, height: float) -> None:
|
||||
self._height = height
|
||||
|
||||
def setThickness(self, thickness):
|
||||
def setThickness(self, thickness: float) -> None:
|
||||
self._thickness = thickness
|
||||
|
||||
def lineMeshVertexCount(self) -> int:
|
||||
|
@ -16,8 +16,7 @@ class LayerData(MeshData):
|
||||
def getLayer(self, layer):
|
||||
if layer in self._layers:
|
||||
return self._layers[layer]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
def getLayers(self):
|
||||
return self._layers
|
||||
|
@ -9,7 +9,7 @@ from cura.LayerData import LayerData
|
||||
|
||||
## Simple decorator to indicate a scene node holds layer data.
|
||||
class LayerDataDecorator(SceneNodeDecorator):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._layer_data = None # type: Optional[LayerData]
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from typing import Any, Optional
|
||||
import numpy
|
||||
|
||||
from typing import Optional, cast
|
||||
|
||||
from UM.Qt.Bindings.Theme import Theme
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
||||
@ -61,7 +62,7 @@ class LayerPolygon:
|
||||
|
||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||
# Should be generated in better way, not hardcoded.
|
||||
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool)
|
||||
self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool)
|
||||
|
||||
self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
|
||||
self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
|
||||
@ -149,17 +150,17 @@ class LayerPolygon:
|
||||
def getColors(self):
|
||||
return self._colors
|
||||
|
||||
def mapLineTypeToColor(self, line_types):
|
||||
def mapLineTypeToColor(self, line_types: numpy.ndarray) -> numpy.ndarray:
|
||||
return self._color_map[line_types]
|
||||
|
||||
def isInfillOrSkinType(self, line_types):
|
||||
return self._isInfillOrSkinTypeMap[line_types]
|
||||
def isInfillOrSkinType(self, line_types: numpy.ndarray) -> numpy.ndarray:
|
||||
return self._is_infill_or_skin_type_map[line_types]
|
||||
|
||||
def lineMeshVertexCount(self):
|
||||
return (self._vertex_end - self._vertex_begin)
|
||||
def lineMeshVertexCount(self) -> int:
|
||||
return self._vertex_end - self._vertex_begin
|
||||
|
||||
def lineMeshElementCount(self):
|
||||
return (self._index_end - self._index_begin)
|
||||
def lineMeshElementCount(self) -> int:
|
||||
return self._index_end - self._index_begin
|
||||
|
||||
@property
|
||||
def extruder(self):
|
||||
@ -202,7 +203,7 @@ class LayerPolygon:
|
||||
return self._jump_count
|
||||
|
||||
# Calculate normals for the entire polygon using numpy.
|
||||
def getNormals(self):
|
||||
def getNormals(self) -> numpy.ndarray:
|
||||
normals = numpy.copy(self._data)
|
||||
normals[:, 1] = 0.0 # We are only interested in 2D normals
|
||||
|
||||
@ -226,13 +227,13 @@ class LayerPolygon:
|
||||
|
||||
return normals
|
||||
|
||||
__color_map = None # type: numpy.ndarray[Any]
|
||||
__color_map = None # type: numpy.ndarray
|
||||
|
||||
## Gets the instance of the VersionUpgradeManager, or creates one.
|
||||
@classmethod
|
||||
def getColorMap(cls):
|
||||
def getColorMap(cls) -> numpy.ndarray:
|
||||
if cls.__color_map is None:
|
||||
theme = QtApplication.getInstance().getTheme()
|
||||
theme = cast(Theme, QtApplication.getInstance().getTheme())
|
||||
cls.__color_map = numpy.array([
|
||||
theme.getColor("layerview_none").getRgbF(), # NoneType
|
||||
theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
|
||||
|
@ -26,7 +26,7 @@ class ContainerNode:
|
||||
## Gets the metadata of the container that this node represents.
|
||||
# Getting the metadata from the container directly is about 10x as fast.
|
||||
# \return The metadata of the container in this node.
|
||||
def getMetadata(self):
|
||||
def getMetadata(self) -> Dict[str, Any]:
|
||||
return ContainerRegistry.getInstance().findContainersMetadata(id = self.container_id)[0]
|
||||
|
||||
## Get an entry from the metadata of the container that this node contains.
|
||||
|
@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
||||
# nodes that have children) but that child node may be a node representing the
|
||||
# empty instance container.
|
||||
class ContainerTree:
|
||||
__instance = None
|
||||
__instance = None # type: Optional["ContainerTree"]
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
@ -75,7 +75,7 @@ class ContainerTree:
|
||||
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
|
||||
|
||||
## Ran after completely starting up the application.
|
||||
def _onStartupFinished(self):
|
||||
def _onStartupFinished(self) -> None:
|
||||
currently_added = ContainerRegistry.getInstance().findContainerStacks() # Find all currently added global stacks.
|
||||
JobQueue.getInstance().add(self._MachineNodeLoadJob(self, currently_added))
|
||||
|
||||
@ -137,7 +137,7 @@ class ContainerTree:
|
||||
# \param container_stacks All of the stacks to pre-load the container
|
||||
# trees for. This needs to be provided from here because the stacks
|
||||
# need to be constructed on the main thread because they are QObject.
|
||||
def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]):
|
||||
def __init__(self, tree_root: "ContainerTree", container_stacks: List["ContainerStack"]) -> None:
|
||||
self.tree_root = tree_root
|
||||
self.container_stacks = container_stacks
|
||||
super().__init__()
|
||||
|
@ -6,13 +6,13 @@ import time
|
||||
from collections import deque
|
||||
|
||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
||||
from typing import Optional, Any, Set
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.SettingDefinition import SettingDefinition
|
||||
from UM.Settings.Validator import ValidatorState
|
||||
|
||||
|
||||
import cura.CuraApplication
|
||||
#
|
||||
# This class performs setting error checks for the currently active machine.
|
||||
#
|
||||
@ -24,25 +24,25 @@ from UM.Settings.Validator import ValidatorState
|
||||
#
|
||||
class MachineErrorChecker(QObject):
|
||||
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, parent: Optional[QObject] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._global_stack = None
|
||||
|
||||
self._has_errors = True # Result of the error check, indicating whether there are errors in the stack
|
||||
self._error_keys = set() # A set of settings keys that have errors
|
||||
self._error_keys_in_progress = set() # The variable that stores the results of the currently in progress check
|
||||
self._error_keys = set() # type: Set[str] # A set of settings keys that have errors
|
||||
self._error_keys_in_progress = set() # type: Set[str] # The variable that stores the results of the currently in progress check
|
||||
|
||||
self._stacks_and_keys_to_check = None # a FIFO queue of tuples (stack, key) to check for errors
|
||||
self._stacks_and_keys_to_check = None # type: Optional[deque] # a FIFO queue of tuples (stack, key) to check for errors
|
||||
|
||||
self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new
|
||||
# error check needs to take place while there is already one running at the moment.
|
||||
self._check_in_progress = False # Whether there is an error check running in progress at the moment.
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
|
||||
self._start_time = 0 # measure checking time
|
||||
self._start_time = 0. # measure checking time
|
||||
|
||||
# This timer delays the starting of error check so we can react less frequently if the user is frequently
|
||||
# changing settings.
|
||||
@ -94,13 +94,13 @@ class MachineErrorChecker(QObject):
|
||||
|
||||
# Start the error check for property changed
|
||||
# this is seperate from the startErrorCheck because it ignores a number property types
|
||||
def startErrorCheckPropertyChanged(self, key, property_name):
|
||||
def startErrorCheckPropertyChanged(self, key: str, property_name: str) -> None:
|
||||
if property_name != "value":
|
||||
return
|
||||
self.startErrorCheck()
|
||||
|
||||
# Starts the error check timer to schedule a new error check.
|
||||
def startErrorCheck(self, *args) -> None:
|
||||
def startErrorCheck(self, *args: Any) -> None:
|
||||
if not self._check_in_progress:
|
||||
self._need_to_check = True
|
||||
self.needToWaitForResultChanged.emit()
|
||||
|
@ -176,9 +176,9 @@ class MachineNode(ContainerNode):
|
||||
|
||||
# Find the global qualities for this printer.
|
||||
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.quality_definition, global_quality = "True") # First try specific to this printer.
|
||||
if len(global_qualities) == 0: # This printer doesn't override the global qualities.
|
||||
if not global_qualities: # This printer doesn't override the global qualities.
|
||||
global_qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter", global_quality = "True") # Otherwise pick the global global qualities.
|
||||
if len(global_qualities) == 0: # There are no global qualities either?! Something went very wrong, but we'll not crash and properly fill the tree.
|
||||
if not global_qualities: # There are no global qualities either?! Something went very wrong, but we'll not crash and properly fill the tree.
|
||||
global_qualities = [cura.CuraApplication.CuraApplication.getInstance().empty_quality_container.getMetaData()]
|
||||
for global_quality in global_qualities:
|
||||
self.global_qualities[global_quality["quality_type"]] = QualityNode(global_quality["id"], parent = self)
|
||||
|
@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
||||
from typing import Dict
|
||||
from cura.Machines.VariantNode import VariantNode
|
||||
|
||||
|
||||
## Represents a material in the container tree.
|
||||
#
|
||||
# Its subcontainers are quality profiles.
|
||||
@ -60,28 +61,38 @@ class MaterialNode(ContainerNode):
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
# Find all quality profiles that fit on this material.
|
||||
if not self.variant.machine.has_machine_quality: # Need to find the global qualities.
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = "fdmprinter")
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality",
|
||||
definition = "fdmprinter")
|
||||
elif not self.variant.machine.has_materials:
|
||||
qualities = container_registry.findInstanceContainersMetadata(type="quality", definition=self.variant.machine.quality_definition)
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality",
|
||||
definition = self.variant.machine.quality_definition)
|
||||
else:
|
||||
if self.variant.machine.has_variants:
|
||||
# Need to find the qualities that specify a material profile with the same material type.
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name, material = self.container_id) # First try by exact material ID.
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality",
|
||||
definition = self.variant.machine.quality_definition,
|
||||
variant = self.variant.variant_name,
|
||||
material = self.base_file) # First try by exact material ID.
|
||||
# CURA-7070
|
||||
# The quality profiles only reference a material with the material_root_id. They will never state something
|
||||
# such as "generic_pla_ultimaker_s5_AA_0.4". So we search with the "base_file" which is the material_root_id.
|
||||
else:
|
||||
qualities = container_registry.findInstanceContainersMetadata(type="quality", definition=self.variant.machine.quality_definition, material=self.container_id)
|
||||
qualities = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, material = self.base_file)
|
||||
if not qualities:
|
||||
my_material_type = self.material_type
|
||||
if self.variant.machine.has_variants:
|
||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition, variant = self.variant.variant_name)
|
||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality",
|
||||
definition = self.variant.machine.quality_definition,
|
||||
variant = self.variant.variant_name)
|
||||
else:
|
||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type="quality", definition = self.variant.machine.quality_definition)
|
||||
qualities_any_material = container_registry.findInstanceContainersMetadata(type = "quality", definition = self.variant.machine.quality_definition)
|
||||
for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", material = my_material_type):
|
||||
qualities.extend((quality for quality in qualities_any_material if quality.get("material") == material_metadata["id"]))
|
||||
qualities.extend((quality for quality in qualities_any_material if quality.get("material") == material_metadata["base_file"]))
|
||||
|
||||
if not qualities: # No quality profiles found. Go by GUID then.
|
||||
my_guid = self.guid
|
||||
for material_metadata in container_registry.findInstanceContainersMetadata(type = "material", guid = my_guid):
|
||||
qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["id"]))
|
||||
qualities.extend((quality for quality in qualities_any_material if quality["material"] == material_metadata["base_file"]))
|
||||
|
||||
if not qualities:
|
||||
# There are still some machines that should use global profiles in the extruder, so do that now.
|
||||
|
@ -45,7 +45,7 @@ class BaseMaterialsModel(ListModel):
|
||||
# can be caused in the middle of a XMLMaterial loading, and the material container we try to find may not be
|
||||
# in the system yet. This will cause an infinite recursion of (1) trying to load a material, (2) trying to
|
||||
# update the material model, (3) cannot find the material container, load it, (4) repeat #1.
|
||||
self._update_timer = QTimer()
|
||||
self._update_timer = QTimer(self)
|
||||
self._update_timer.setInterval(100)
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._update)
|
||||
|
@ -204,7 +204,7 @@ class DiscoveredPrintersModel(QObject):
|
||||
@pyqtProperty("QVariantMap", notify = discoveredPrintersChanged)
|
||||
def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]:
|
||||
return self._discovered_printer_by_ip_dict
|
||||
|
||||
|
||||
@pyqtProperty("QVariantList", notify = discoveredPrintersChanged)
|
||||
def discoveredPrinters(self) -> List["DiscoveredPrinter"]:
|
||||
item_list = list(
|
||||
|
@ -2,7 +2,7 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional, Dict, Any, Set, List
|
||||
|
||||
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal
|
||||
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal, QTimer
|
||||
|
||||
import cura.CuraApplication
|
||||
from UM.Qt.ListModel import ListModel
|
||||
@ -32,9 +32,14 @@ class IntentModel(ListModel):
|
||||
|
||||
self._intent_category = "engineering"
|
||||
|
||||
self._update_timer = QTimer()
|
||||
self._update_timer.setInterval(100)
|
||||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._update)
|
||||
|
||||
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
||||
machine_manager.globalContainerChanged.connect(self._update)
|
||||
machine_manager.extruderChanged.connect(self._update) # We also need to update if an extruder gets disabled
|
||||
machine_manager.globalContainerChanged.connect(self._updateDelayed)
|
||||
machine_manager.extruderChanged.connect(self._updateDelayed) # We also need to update if an extruder gets disabled
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onChanged)
|
||||
ContainerRegistry.getInstance().containerRemoved.connect(self._onChanged)
|
||||
self._layer_height_unit = "" # This is cached
|
||||
@ -52,9 +57,12 @@ class IntentModel(ListModel):
|
||||
def intentCategory(self) -> str:
|
||||
return self._intent_category
|
||||
|
||||
def _updateDelayed(self):
|
||||
self._update_timer.start()
|
||||
|
||||
def _onChanged(self, container):
|
||||
if container.getMetaDataEntry("type") == "intent":
|
||||
self._update()
|
||||
self._updateDelayed()
|
||||
|
||||
def _update(self) -> None:
|
||||
new_items = [] # type: List[Dict[str, Any]]
|
||||
|
@ -77,6 +77,10 @@ class SettingVisibilityPresetsModel(QObject):
|
||||
|
||||
items.append(setting_visibility_preset)
|
||||
|
||||
# Add the "all" visibility:
|
||||
all_setting_visibility_preset = SettingVisibilityPreset(preset_id = "all", name = "All", weight = 9001)
|
||||
all_setting_visibility_preset.setSettings(list(CuraApplication.getInstance().getMachineManager().getAllSettingKeys()))
|
||||
items.append(all_setting_visibility_preset)
|
||||
# Sort them on weight (and if that fails, use ID)
|
||||
items.sort(key = lambda k: (int(k.weight), k.presetId))
|
||||
|
||||
|
@ -41,4 +41,4 @@ class QualityNode(ContainerNode):
|
||||
self.intents[intent["id"]] = IntentNode(intent["id"], quality = self)
|
||||
|
||||
self.intents["empty_intent"] = IntentNode("empty_intent", quality = self)
|
||||
# Otherwise, there are no intents for global profiles.
|
||||
# Otherwise, there are no intents for global profiles.
|
||||
|
@ -47,7 +47,7 @@ class MultiplyObjectsJob(Job):
|
||||
nodes = []
|
||||
|
||||
not_fit_count = 0
|
||||
|
||||
found_solution_for_all = False
|
||||
for node in self._objects:
|
||||
# If object is part of a group, multiply group
|
||||
current_node = node
|
||||
@ -66,7 +66,7 @@ class MultiplyObjectsJob(Job):
|
||||
|
||||
found_solution_for_all = True
|
||||
arranger.resetLastPriority()
|
||||
for i in range(self._count):
|
||||
for _ in range(self._count):
|
||||
# We do place the nodes one by one, as we want to yield in between.
|
||||
new_node = copy.deepcopy(node)
|
||||
solution_found = False
|
||||
@ -98,10 +98,10 @@ class MultiplyObjectsJob(Job):
|
||||
Job.yieldThread()
|
||||
|
||||
if nodes:
|
||||
op = GroupedOperation()
|
||||
operation = GroupedOperation()
|
||||
for new_node in nodes:
|
||||
op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
|
||||
op.push()
|
||||
operation.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
|
||||
operation.push()
|
||||
status_message.hide()
|
||||
|
||||
if not found_solution_for_all:
|
||||
|
@ -1,10 +1,10 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
|
||||
class BaseModel:
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
|
||||
@ -53,9 +53,10 @@ class ResponseData(BaseModel):
|
||||
redirect_uri = None # type: Optional[str]
|
||||
content_type = "text/html" # type: str
|
||||
|
||||
|
||||
## Possible HTTP responses.
|
||||
HTTP_STATUS = {
|
||||
"OK": ResponseStatus(code = 200, message = "OK"),
|
||||
"NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"),
|
||||
"REDIRECT": ResponseStatus(code = 302, message = "REDIRECT")
|
||||
}
|
||||
} # type: Dict[str, ResponseStatus]
|
||||
|
@ -1,26 +1,27 @@
|
||||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Operations.Operation import Operation
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
|
||||
## A specialised operation designed specifically to modify the previous operation.
|
||||
class PlatformPhysicsOperation(Operation):
|
||||
def __init__(self, node, translation):
|
||||
def __init__(self, node: SceneNode, translation: Vector):
|
||||
super().__init__()
|
||||
self._node = node
|
||||
self._old_transformation = node.getLocalTransformation()
|
||||
self._translation = translation
|
||||
self._always_merge = True
|
||||
|
||||
def undo(self):
|
||||
def undo(self) -> None:
|
||||
self._node.setTransformation(self._old_transformation)
|
||||
|
||||
def redo(self):
|
||||
def redo(self) -> None:
|
||||
self._node.translate(self._translation, SceneNode.TransformSpace.World)
|
||||
|
||||
def mergeWith(self, other):
|
||||
def mergeWith(self, other: Operation) -> GroupedOperation:
|
||||
group = GroupedOperation()
|
||||
|
||||
group.addOperation(other)
|
||||
@ -28,5 +29,5 @@ class PlatformPhysicsOperation(Operation):
|
||||
|
||||
return group
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return "PlatformPhysicsOp.(trans.={0})".format(self._translation)
|
||||
|
@ -6,9 +6,9 @@ from UM.Operations.Operation import Operation
|
||||
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
|
||||
## Simple operation to set the buildplate number of a scenenode.
|
||||
class SetBuildPlateNumberOperation(Operation):
|
||||
|
||||
def __init__(self, node: SceneNode, build_plate_nr: int) -> None:
|
||||
super().__init__()
|
||||
self._node = node
|
||||
@ -16,11 +16,11 @@ class SetBuildPlateNumberOperation(Operation):
|
||||
self._previous_build_plate_nr = None
|
||||
self._decorator_added = False
|
||||
|
||||
def undo(self):
|
||||
def undo(self) -> None:
|
||||
if self._previous_build_plate_nr:
|
||||
self._node.callDecoration("setBuildPlateNumber", self._previous_build_plate_nr)
|
||||
|
||||
def redo(self):
|
||||
def redo(self) -> None:
|
||||
stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
|
||||
if not stack:
|
||||
self._node.addDecorator(SettingOverrideDecorator())
|
||||
|
@ -1,36 +1,37 @@
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Uranium is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional
|
||||
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Operations import Operation
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
|
||||
## An operation that parents a scene node to another scene node.
|
||||
|
||||
## An operation that parents a scene node to another scene node.
|
||||
class SetParentOperation(Operation.Operation):
|
||||
## Initialises this SetParentOperation.
|
||||
#
|
||||
# \param node The node which will be reparented.
|
||||
# \param parent_node The node which will be the parent.
|
||||
def __init__(self, node, parent_node):
|
||||
def __init__(self, node: SceneNode, parent_node: Optional[SceneNode]):
|
||||
super().__init__()
|
||||
self._node = node
|
||||
self._parent = parent_node
|
||||
self._old_parent = node.getParent() # To restore the previous parent in case of an undo.
|
||||
|
||||
## Undoes the set-parent operation, restoring the old parent.
|
||||
def undo(self):
|
||||
def undo(self) -> None:
|
||||
self._set_parent(self._old_parent)
|
||||
|
||||
## Re-applies the set-parent operation.
|
||||
def redo(self):
|
||||
def redo(self) -> None:
|
||||
self._set_parent(self._parent)
|
||||
|
||||
## Sets the parent of the node while applying transformations to the world-transform of the node stays the same.
|
||||
#
|
||||
# \param new_parent The new parent. Note: this argument can be None, which would hide the node from the scene.
|
||||
def _set_parent(self, new_parent):
|
||||
def _set_parent(self, new_parent: Optional[SceneNode]) -> None:
|
||||
if new_parent:
|
||||
current_parent = self._node.getParent()
|
||||
if current_parent:
|
||||
@ -59,5 +60,5 @@ class SetParentOperation(Operation.Operation):
|
||||
## Returns a programmer-readable representation of this operation.
|
||||
#
|
||||
# \return A programmer-readable representation of this operation.
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return "SetParentOperation(node = {0}, parent_node={1})".format(self._node, self._parent)
|
||||
|
@ -17,9 +17,6 @@ from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from UM.Scene.Camera import Camera
|
||||
|
||||
|
||||
|
@ -3,6 +3,7 @@ from PyQt5.QtQuick import QQuickImageProvider
|
||||
from PyQt5.QtCore import QSize
|
||||
|
||||
from UM.Application import Application
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||
@ -10,7 +11,7 @@ class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||
super().__init__(QQuickImageProvider.Image)
|
||||
|
||||
## Request a new image.
|
||||
def requestImage(self, id: str, size: QSize) -> QImage:
|
||||
def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]:
|
||||
# The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
|
||||
# increment, we need to strip that first.
|
||||
uuid = id[id.find("/") + 1:]
|
||||
@ -22,6 +23,6 @@ class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||
if print_job.key == uuid:
|
||||
if print_job.getPreviewImage():
|
||||
return print_job.getPreviewImage(), QSize(15, 15)
|
||||
else:
|
||||
return QImage(), QSize(15, 15)
|
||||
return QImage(), QSize(15,15)
|
||||
|
||||
return QImage(), QSize(15, 15)
|
||||
return QImage(), QSize(15, 15)
|
@ -161,7 +161,7 @@ class PrintJobOutputModel(QObject):
|
||||
self._time_elapsed = new_time_elapsed
|
||||
self.timeElapsedChanged.emit()
|
||||
|
||||
def updateState(self, new_state):
|
||||
def updateState(self, new_state: str) -> None:
|
||||
if self._state != new_state:
|
||||
self._state = new_state
|
||||
self.stateChanged.emit()
|
||||
|
@ -148,7 +148,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
|
||||
@pyqtProperty(QObject, notify = printersChanged)
|
||||
def activePrinter(self) -> Optional["PrinterOutputModel"]:
|
||||
if len(self._printers):
|
||||
if self._printers:
|
||||
return self._printers[0]
|
||||
return None
|
||||
|
||||
|
@ -10,3 +10,6 @@ class BlockSlicingDecorator(SceneNodeDecorator):
|
||||
|
||||
def isBlockSlicing(self) -> bool:
|
||||
return True
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return BlockSlicingDecorator()
|
@ -1,6 +1,6 @@
|
||||
from UM.Logger import Logger
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, QObject
|
||||
from PyQt5.QtCore import Qt, pyqtSlot, QObject, QTimer
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from UM.Scene.Camera import Camera
|
||||
@ -26,16 +26,23 @@ class CuraSceneController(QObject):
|
||||
|
||||
self._last_selected_index = 0
|
||||
self._max_build_plate = 1 # default
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(100)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self.updateMaxBuildPlate)
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self.updateMaxBuildPlateDelayed)
|
||||
|
||||
Application.getInstance().getController().getScene().sceneChanged.connect(self.updateMaxBuildPlate) # it may be a bit inefficient when changing a lot simultaneously
|
||||
|
||||
def updateMaxBuildPlate(self, *args):
|
||||
def updateMaxBuildPlateDelayed(self, *args):
|
||||
if args:
|
||||
source = args[0]
|
||||
else:
|
||||
source = None
|
||||
|
||||
if not isinstance(source, SceneNode) or isinstance(source, Camera):
|
||||
return
|
||||
self._change_timer.start()
|
||||
|
||||
def updateMaxBuildPlate(self, *args):
|
||||
max_build_plate = self._calcMaxBuildPlate()
|
||||
changed = False
|
||||
if max_build_plate != self._max_build_plate:
|
||||
|
@ -17,8 +17,8 @@ class GCodeListDecorator(SceneNodeDecorator):
|
||||
def getGCodeList(self) -> List[str]:
|
||||
return self._gcode_list
|
||||
|
||||
def setGCodeList(self, list: List[str]) -> None:
|
||||
self._gcode_list = list
|
||||
def setGCodeList(self, gcode_list: List[str]) -> None:
|
||||
self._gcode_list = gcode_list
|
||||
|
||||
def __deepcopy__(self, memo) -> "GCodeListDecorator":
|
||||
copied_decorator = GCodeListDecorator()
|
||||
|
@ -15,7 +15,6 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Platform import Platform
|
||||
@ -176,7 +175,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
if not file_name:
|
||||
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>: {1}", file_name, "Invalid path")}
|
||||
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return {"status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Can't import profile from <filename>{0}</filename> before a printer is added.", file_name)}
|
||||
container_tree = ContainerTree.getInstance()
|
||||
@ -384,7 +383,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
if not quality_type:
|
||||
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return None
|
||||
definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
|
@ -43,7 +43,7 @@ class CuraFormulaFunctions:
|
||||
extruder_stack = global_stack.extruderList[int(extruder_position)]
|
||||
except IndexError:
|
||||
if extruder_position != 0:
|
||||
Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. Returning the result form extruder 0 instead" % (property_key, extruder_position))
|
||||
Logger.log("w", "Value for %s of extruder %s was requested, but that extruder is not available. Returning the result from extruder 0 instead" % (property_key, extruder_position))
|
||||
# This fixes a very specific fringe case; If a profile was created for a custom printer and one of the
|
||||
# extruder settings has been set to non zero and the profile is loaded for a machine that has only a single extruder
|
||||
# it would cause all kinds of issues (and eventually a crash).
|
||||
|
@ -4,12 +4,11 @@
|
||||
import time
|
||||
import re
|
||||
import unicodedata
|
||||
from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast
|
||||
from typing import Any, List, Dict, TYPE_CHECKING, Optional, cast, Set
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Decorators import deprecated
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
@ -83,13 +82,9 @@ class MachineManager(QObject):
|
||||
|
||||
self._stacks_have_errors = None # type: Optional[bool]
|
||||
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
extruder_manager = self._application.getExtruderManager()
|
||||
|
||||
extruder_manager.activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
||||
self._onActiveExtruderStackChanged()
|
||||
|
||||
extruder_manager.activeExtruderChanged.connect(self.activeMaterialChanged)
|
||||
extruder_manager.activeExtruderChanged.connect(self.activeVariantChanged)
|
||||
extruder_manager.activeExtruderChanged.connect(self.activeQualityChanged)
|
||||
@ -212,10 +207,13 @@ class MachineManager(QObject):
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def totalNumberOfSettings(self) -> int:
|
||||
general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
|
||||
return len(self.getAllSettingKeys())
|
||||
|
||||
def getAllSettingKeys(self) -> Set[str]:
|
||||
general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")
|
||||
if not general_definition_containers:
|
||||
return 0
|
||||
return len(general_definition_containers[0].getAllKeys())
|
||||
return set()
|
||||
return general_definition_containers[0].getAllKeys()
|
||||
|
||||
## Triggered when the global container stack is changed in CuraApplication.
|
||||
def _onGlobalContainerChanged(self) -> None:
|
||||
@ -229,7 +227,7 @@ class MachineManager(QObject):
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
for extruder_stack in self._global_container_stack.extruderList:
|
||||
extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
extruder_stack.containersChanged.disconnect(self._onContainersChanged)
|
||||
|
||||
@ -259,7 +257,7 @@ class MachineManager(QObject):
|
||||
self._global_container_stack.setMaterial(empty_material_container)
|
||||
|
||||
# Listen for changes on all extruder stacks
|
||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
for extruder_stack in self._global_container_stack.extruderList:
|
||||
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
extruder_stack.containersChanged.connect(self._onContainersChanged)
|
||||
|
||||
@ -367,7 +365,7 @@ class MachineManager(QObject):
|
||||
|
||||
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
|
||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
extruder_stacks = self._global_container_stack.extruderList
|
||||
count = 1 # We start with the global stack
|
||||
for stack in extruder_stacks:
|
||||
md = stack.getMetaData()
|
||||
@ -390,8 +388,7 @@ class MachineManager(QObject):
|
||||
if self._global_container_stack.getTop().getNumInstances() != 0:
|
||||
return True
|
||||
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for stack in stacks:
|
||||
for stack in self._global_container_stack.extruderList:
|
||||
if stack.getTop().getNumInstances() != 0:
|
||||
return True
|
||||
|
||||
@ -401,8 +398,7 @@ class MachineManager(QObject):
|
||||
def numUserSettings(self) -> int:
|
||||
if not self._global_container_stack:
|
||||
return 0
|
||||
num_user_settings = 0
|
||||
num_user_settings += self._global_container_stack.getTop().getNumInstances()
|
||||
num_user_settings = self._global_container_stack.getTop().getNumInstances()
|
||||
stacks = self._global_container_stack.extruderList
|
||||
for stack in stacks:
|
||||
num_user_settings += stack.getTop().getNumInstances()
|
||||
@ -429,7 +425,7 @@ class MachineManager(QObject):
|
||||
stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
stacks = [stack]
|
||||
else:
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
stacks = self._global_container_stack.extruderList
|
||||
|
||||
for stack in stacks:
|
||||
if stack is not None:
|
||||
@ -614,10 +610,9 @@ class MachineManager(QObject):
|
||||
if self._active_container_stack is None or self._global_container_stack is None:
|
||||
return
|
||||
new_value = self._active_container_stack.getProperty(key, "value")
|
||||
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()]
|
||||
|
||||
# Check in which stack the value has to be replaced
|
||||
for extruder_stack in extruder_stacks:
|
||||
for extruder_stack in self._global_container_stack.extruderList:
|
||||
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
|
||||
|
||||
@ -894,8 +889,8 @@ class MachineManager(QObject):
|
||||
|
||||
@pyqtSlot(int, bool)
|
||||
def setExtruderEnabled(self, position: int, enabled: bool) -> None:
|
||||
if self._global_container_stack is None:
|
||||
Logger.log("w", "Could not find extruder on position %s", position)
|
||||
if self._global_container_stack is None or str(position) not in self._global_container_stack.extruders:
|
||||
Logger.log("w", "Could not find extruder on position %s.", position)
|
||||
return
|
||||
extruder = self._global_container_stack.extruderList[position]
|
||||
|
||||
@ -931,7 +926,7 @@ class MachineManager(QObject):
|
||||
def _getContainerChangedSignals(self) -> List[Signal]:
|
||||
if self._global_container_stack is None:
|
||||
return []
|
||||
return [s.containersChanged for s in ExtruderManager.getInstance().getActiveExtruderStacks() + [self._global_container_stack]]
|
||||
return [s.containersChanged for s in self._global_container_stack.extruderList + [self._global_container_stack]]
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str) -> None:
|
||||
|
@ -9,6 +9,7 @@ from UM.Resources import Resources
|
||||
from UM.Application import Application
|
||||
from cura import ApplicationMetadata
|
||||
|
||||
import time
|
||||
|
||||
class CuraSplashScreen(QSplashScreen):
|
||||
def __init__(self):
|
||||
@ -34,15 +35,20 @@ class CuraSplashScreen(QSplashScreen):
|
||||
self._change_timer.setSingleShot(False)
|
||||
self._change_timer.timeout.connect(self.updateLoadingImage)
|
||||
|
||||
self._last_update_time = None
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
self._last_update_time = time.time()
|
||||
self._change_timer.start()
|
||||
|
||||
def updateLoadingImage(self):
|
||||
if self._to_stop:
|
||||
return
|
||||
|
||||
self._loading_image_rotation_angle -= 10
|
||||
time_since_last_update = time.time() - self._last_update_time
|
||||
self._last_update_time = time.time()
|
||||
# Since we don't know how much time actually passed, check how many intervals of 50 we had.
|
||||
self._loading_image_rotation_angle -= 10 * (time_since_last_update * 1000 / 50)
|
||||
self.repaint()
|
||||
|
||||
# Override the mousePressEvent so the splashscreen doesn't disappear when clicked
|
||||
|
@ -43,7 +43,7 @@ class MachineActionManager(QObject):
|
||||
# Dict of all actions that need to be done when first added by definition ID
|
||||
self._first_start_actions = {} # type: Dict[str, List[MachineAction]]
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
# Add machine_action as plugin type
|
||||
PluginRegistry.addType("machine_action", self.addMachineAction)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import os
|
||||
import unicodedata
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot, QTimer
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.Duration import Duration
|
||||
@ -47,7 +47,12 @@ class PrintInformation(QObject):
|
||||
if self._backend:
|
||||
self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
|
||||
|
||||
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
|
||||
self._application.getController().getScene().sceneChanged.connect(self._onSceneChangedDelayed)
|
||||
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(100)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self._onSceneChanged)
|
||||
|
||||
self._is_user_specified_job_name = False
|
||||
self._base_name = ""
|
||||
@ -418,12 +423,14 @@ class PrintInformation(QObject):
|
||||
|
||||
self._onPrintDurationMessage(build_plate, temp_message, temp_material_amounts)
|
||||
|
||||
## Listen to scene changes to check if we need to reset the print information
|
||||
def _onSceneChanged(self, scene_node: SceneNode) -> None:
|
||||
def _onSceneChangedDelayed(self, scene_node: SceneNode) -> None:
|
||||
# Ignore any changes that are not related to sliceable objects
|
||||
if not isinstance(scene_node, SceneNode)\
|
||||
or not scene_node.callDecoration("isSliceable")\
|
||||
if not isinstance(scene_node, SceneNode) \
|
||||
or not scene_node.callDecoration("isSliceable") \
|
||||
or not scene_node.callDecoration("getBuildPlateNumber") == self._active_build_plate:
|
||||
return
|
||||
self._change_timer.start()
|
||||
|
||||
## Listen to scene changes to check if we need to reset the print information
|
||||
def _onSceneChanged(self) -> None:
|
||||
self.setToZeroPrintInformation(self._active_build_plate)
|
||||
|
25
cura_app.py
25
cura_app.py
@ -9,8 +9,15 @@ import os
|
||||
import sys
|
||||
|
||||
from UM.Platform import Platform
|
||||
from cura import ApplicationMetadata
|
||||
from cura.ApplicationMetadata import CuraAppName
|
||||
|
||||
try:
|
||||
import sentry_sdk
|
||||
with_sentry_sdk = True
|
||||
except ImportError:
|
||||
with_sentry_sdk = False
|
||||
|
||||
parser = argparse.ArgumentParser(prog = "cura",
|
||||
add_help = False)
|
||||
parser.add_argument("--debug",
|
||||
@ -18,8 +25,26 @@ parser.add_argument("--debug",
|
||||
default = False,
|
||||
help = "Turn on the debug mode by setting this option."
|
||||
)
|
||||
|
||||
known_args = vars(parser.parse_known_args()[0])
|
||||
|
||||
if with_sentry_sdk:
|
||||
sentry_env = "production"
|
||||
if ApplicationMetadata.CuraVersion == "master":
|
||||
sentry_env = "development"
|
||||
try:
|
||||
if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
|
||||
sentry_env = "nightly"
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
sentry_sdk.init("https://5034bf0054fb4b889f82896326e79b13@sentry.io/1821564",
|
||||
environment = sentry_env,
|
||||
release = "cura%s" % ApplicationMetadata.CuraVersion,
|
||||
default_integrations = False,
|
||||
max_breadcrumbs = 300,
|
||||
server_name = "cura")
|
||||
|
||||
if not known_args["debug"]:
|
||||
def get_cura_dir_path():
|
||||
if Platform.isWindows():
|
||||
|
@ -20,7 +20,16 @@ cd "${PROJECT_DIR}"
|
||||
# Check the branch to use:
|
||||
# 1. Use the Uranium branch with the branch same if it exists.
|
||||
# 2. Otherwise, use the default branch name "master"
|
||||
URANIUM_BRANCH="${CI_COMMIT_REF_NAME:-master}"
|
||||
echo "GITHUB_REF: ${GITHUB_REF}"
|
||||
echo "GITHUB_BASE_REF: ${GITHUB_BASE_REF}"
|
||||
|
||||
GIT_REF_NAME="${GITHUB_REF}"
|
||||
if [ -n "${GITHUB_BASE_REF}" ]; then
|
||||
GIT_REF_NAME="${GITHUB_BASE_REF}"
|
||||
fi
|
||||
GIT_REF_NAME="$(basename "${GIT_REF_NAME}")"
|
||||
|
||||
URANIUM_BRANCH="${GIT_REF_NAME:-master}"
|
||||
output="$(git ls-remote --heads https://github.com/Ultimaker/Uranium.git "${URANIUM_BRANCH}")"
|
||||
if [ -z "${output}" ]; then
|
||||
echo "Could not find Uranium banch ${URANIUM_BRANCH}, fallback to use master."
|
||||
|
1
docs/Profile Stack.drawio
Normal file
1
docs/Profile Stack.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="www.draw.io" modified="2019-12-20T12:41:33.716Z" agent="Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" etag="exp7abRcULgdJsv-qAei" version="12.4.3" type="device" pages="1"><diagram id="05EojhSyumsKE0fvOSX8" name="Page-1">7ZtNb9s4EIZ/jY+70LftY+NkuwskQFrvts2pYCRaIkqLBkXXcn79Di3Ssk3ZTR195EDAB82IpIZ8ZgzxBTXyZ8vyI0er7IElmI48JylH/u3I89zA80by5yTbyjOOxpUj5SRRjWrHnLxg5XSUd00SXBw1FIxRQVbHzpjlOY7FkQ9xzjbHzRaMHj91hVJsOOYxoqb3K0lEVnkn3rj2/41Jmuknu9G0urNEurGaSZGhhG0OXP7dyJ9xxkR1tSxnmMrF0+tS9fvrzN19YBzn4jUdvnDvJQzwv+sXOp0/lfHk+3fxh1+N8hPRtZrwXSn4OsF8LlD8Q0Uutno5ig1ZUpSDdbNguZirOw7YcUZoco+2bC3DKWR3bd1kjJMXaI8o3HLBAbe5ULS9SI5GKJ0xyjg4crZ7QN2piqV6DMcFdHvU03ZPXA+oPGp4jwqhA2SUolVBnnchy45LxFOS3zAh2FI1UuuBucDl2YV29/gg7zFbYsG30ER3mCriKuXdsbI3dQK5gfJlB8nj+YFKXJW06X7smitcKLS/gTkwMP9XYG7QhTmLHRzOfuATGg2AECVpDibFC9lNLhqByvmg3IKt5GArFJM8vd+1uQ1qz2c1celi0HdBd9WRkSTBueTHBBLoeZ9fK0ZysVuY8AZ+sH4z589wFELgM7Dd2oafbM7FjOUwF0R23DBkwgbLbHgd5PP1YpJXpCGTXwc66ohzaHD+tAZKQsY4y1Cewl+phd4u9NAbGHpkQP8nF3JGlnS7pMeTgUmPz5e3Rd0qatcJBmY9MVg/IIE5gVcoC7tl2P5rX9C6gj01YH9BgNr+h7fPOhz6HU3vbA9g3+IFyYkgLLfvad2Rnwz9oua6F8lb3u1uxpyhX9c8k/dHyp61nmG1lbdrK4GWEw8g+06v2opWOK248tZ6Psf+vLrSjLqzejbVUiuvdI69QV/pGbupnlqBpRvWDQpLz6wvKKgWdruwmzSWnmmb0qkVWTrD3aCy9Izb1E+tzNIV7QadpWfapoJqdZZe0DcILT2jN/VUK7R0uDFrUFr6Be6ae3B7juWtWosfvrtzLK65A7day1UlXVXM+z3J4pqbbqu1dI598LMsrrn/tlpLN6wHP83imrtvq7V0BHv48yyuufm2WktnuAc/0eKau2+rtXRFe/gzLZc33PaFrTP0gx9q0Z+3WK2ln43Z4KdafBO4gRnnyQf58RVYMUVFQWKpg4illk1gCfj2m1x2WE9lPikKO+O2PLK22iqJOOgG1pMeEa7rTtLQfargcGJ853Wig8AE2JrH+NLUq3YC8RRfo5cdMAsbkGkfxxQJ8vM43iaO6gmPMm9H58/I6BLVQ1TzVL3qbDAG8oLjgaLpyUDVQhgDAXq0PWim6upswF50ErDvXIzLDy62h4sqgjrH9wzekPbmYa7fT/sr0lcaj/CaDPFjflgUV5RPi6Wgc+qd10LknaTK+MpaiMa/KKqWaiE4DbjfWgCz/iK1al5/1+vf/Q8=</diagram></mxfile>
|
BIN
docs/Profile Stack.png
Normal file
BIN
docs/Profile Stack.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
1
docs/Profile Structure.drawio
Normal file
1
docs/Profile Structure.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="www.draw.io" modified="2019-12-20T12:34:56.339Z" agent="Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" etag="1NLsmsxIqXUmOJee4m9D" version="12.4.3" type="device" pages="1"><diagram id="K0t5C8WxT4tyKudoHXNk" name="Page-1">7VzbcqM4EP0aP+4WSFzsx8SZmd2tpCqTbO1kn1KKkW3VYOQBObHn61cyF0MLM9jhkmyo8gMSLXQ5R+rTDckIT1fbLyFZL2+4R/0RMrztCF+NEDIthEbqZ3i7uMZ13LhiETIvMTpU3LOfNKk0ktoN82hUMBSc+4Kti5UzHgR0Jgp1JAz5S9Fszv1ir2uyoFrF/Yz4eu035ollXDtG7qH+D8oWy7Rn05nEd1YkNU5mEi2Jx19yVfjTCE9DzkV8tdpOqa8WL12XuN3nI3ezgYU0EHUa/OXdPfz4exV+fRLWl92tG9388/m35CnPxN8kE/4zEOqB8ZDFLl2H6IWtfBLI0uWcB+I+uWPI8mzJfO+a7PhGjSMSZPY9LV0uech+Snviy1umrJC3Q5HAjFXrOfP9Kfd5KCsCvu/g0OhePSzpJqSRbHabztcEVTdkWzC8JpFIB8h9n6wj9rQfsmq4IuGCBZdcCL5KjJKFoKGg26MrbGa4ScJTvqIi3EmTtIGTQJ1w3URWXH45MMecJDbLPGvShiRh6yJ7dtbdnWQ3CRZyCll/2SZJ+7MMrT+npDvkFHsjvqBhQAS95JvAi/Iskhe5mR6q9tw6gWdY4xnb8+xxJrtdcDVTOSi8vciQyHFPIiL21An5dwq4UkIf4rNFIIs+natmClImN/RFUi34Wj1sTWYsWFzvba6sQ81dsk6qisu2c3+/aZfM82ig2MUFEeQpY/+ay5nsF9K+lD+53FPjd3tky4FPZdk8lOVPmYdiygM5F8L2rKKSpy9UcbUeBY9vY52XCS+wTotSGkJe5FlYIMSp6Fsa+vJ0pCGTp8KAdbNY207PWE80rD06ZwETjAcD2g2jPUY9o516oBzcz0RubKUghgO9LdhNY9w37rpw/LGROIndo9it6YB404jjmlKyNcRtDfCvMeCdhApyWv+vUMEtSndslmg0qyxUwBWhwqsAdgaR1txutit3MyoRaWVgt7aZ3eH07hJvu0SmdYr3eFBp3aPuloi0TlE39SM9H4sNyLeFvGmUqLVuoddPeA1lGngXKkMuSzOfRBGbKUElVqn+olsmHpJFV9f/qmu5snHpapu7dbVLC4Ec/UO+kGuliodm+1LaLh4c9bRkPBBUcgJ8E85o1dTT1wRSuNEqDN1yDHOY2SWQpXUh9Ylgz8XxVuRvbxVtcwJwXBSAaAIeEc8zaZVP64MHZe8XMl8DHhSvg/YgCT3Z5cySbXV8wBgM2DUrxwXtsWsDVscjaDTNbOqO7qiIHcKUX4cpFgDRrKtkshOw+ZNNTzA+kYg+ylUfhOsZaYdxpSfrPVJBeoJxyCe3h3fvkQrSE4tZqDKA3SzYvQcoqSh9lUo9U3Gm6jZVunXUbYMq1aopUmN315dKxSBNmanWU1WqBT5VQJZbS6U2JQyR/gr62LEy6MLTdSEa1wx40yOn+aNEfz8RkNWgCE93GvFWecOKsDqrNeDdLN79K0I9pv8oImFcUySgI+nIbkSCbYAMlHumSLBtmMoCD2oolQXFiGUYleOC9hhhwOoWUln4pMB3EC2/Fi0OyJRip+bRlh6BjR9tWA92pf3gwk51YdgoB/6tSBasRx+nu7AzXNFbeBvj1PRh+Mjm7caHWeDTffvcQNcGvsKG3+s05cMs4MOSv0g56sOgvVF4fdOSD9ODso/Ce/Q+eI9AIO+cy3voXG2rHd7bE+DErUn1fpz0wXs9RO2W92Z/vDfeBe8d+HYahhp1ee+CDKkFN1BDvHcBj5FZfd5D+2543+BXJ++N93W/Ouk3VncMSItzz3sQq2O7Hd7DASMLV4/LgBu7i1hd/ybhdN6fyeFzvtJqkPeTd3Hcm4AVDlTldWlvwhRVS8e9Cd+XmdUyB9pbdnVKS3MPp9p34U4sPT3SrTvpMWyuK6P6dSdaZnRy5r6C8TduKXzQ30Pb1ePqI/VroQ/L+7pRc7+fRcDX35ror0178BWw1VK2CI/h9qp2J9DenLzquJfFw/85ic0P/y0Gf/oP</diagram></mxfile>
|
BIN
docs/Profile Structure.png
Normal file
BIN
docs/Profile Structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
@ -750,7 +750,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
|
||||
quality_changes_info = self._machine_info.quality_changes_info
|
||||
quality_changes_quality_type = quality_changes_info.global_info.parser["metadata"]["quality_type"]
|
||||
quality_changes_intent_category_per_extruder = {position: info.parser["metadata"].get("intent_category", "default") for position, info in quality_changes_info.extruder_info_dict.items()}
|
||||
|
||||
# quality changes container may not be present for every extruder. Prepopulate the dict with default values.
|
||||
quality_changes_intent_category_per_extruder = {position: "default" for position in self._machine_info.extruder_info_dict}
|
||||
for position, info in quality_changes_info.extruder_info_dict.items():
|
||||
quality_changes_intent_category_per_extruder[position] = info.parser["metadata"].get("intent_category", "default")
|
||||
|
||||
quality_changes_name = quality_changes_info.name
|
||||
create_new = self._resolve_strategies.get("quality_changes") != "override"
|
||||
@ -790,7 +794,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
container_info = ContainerInfo(None, None, None)
|
||||
quality_changes_info.extruder_info_dict["0"] = container_info
|
||||
# If the global stack we're "targeting" has never been active, but was updated from Cura 3.4,
|
||||
# it might not have it's extruders set properly.
|
||||
# it might not have its extruders set properly.
|
||||
if not global_stack.extruders:
|
||||
ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||
extruder_stack = global_stack.extruders["0"]
|
||||
@ -1001,8 +1005,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
|
||||
# Set metadata fields that are missing from the global stack
|
||||
for key, value in self._machine_info.metadata_dict.items():
|
||||
if key not in global_stack.getMetaData():
|
||||
global_stack.setMetaDataEntry(key, value)
|
||||
global_stack.setMetaDataEntry(key, value)
|
||||
|
||||
def _updateActiveMachine(self, global_stack):
|
||||
# Actually change the active machine.
|
||||
|
@ -153,7 +153,7 @@ class StartSliceJob(Job):
|
||||
self.setResult(StartJobResult.MaterialIncompatible)
|
||||
return
|
||||
|
||||
for position, extruder_stack in stack.extruders.items():
|
||||
for extruder_stack in stack.extruderList:
|
||||
material = extruder_stack.findContainer({"type": "material"})
|
||||
if not extruder_stack.isEnabled:
|
||||
continue
|
||||
@ -162,7 +162,6 @@ class StartSliceJob(Job):
|
||||
self.setResult(StartJobResult.MaterialIncompatible)
|
||||
return
|
||||
|
||||
|
||||
# Don't slice if there is a per object setting with an error value.
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
||||
|
@ -5,7 +5,7 @@ import numpy
|
||||
|
||||
import math
|
||||
|
||||
from PyQt5.QtGui import QImage, qRed, qGreen, qBlue
|
||||
from PyQt5.QtGui import QImage, qRed, qGreen, qBlue, qAlpha
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from UM.Mesh.MeshReader import MeshReader
|
||||
@ -138,6 +138,11 @@ class ImageReader(MeshReader):
|
||||
height_data *= scale_vector.y
|
||||
height_data += base_height
|
||||
|
||||
if img.hasAlphaChannel():
|
||||
for x in range(0, width):
|
||||
for y in range(0, height):
|
||||
height_data[y, x] *= qAlpha(img.pixel(x, y)) / 255.0
|
||||
|
||||
heightmap_face_count = 2 * height_minus_one * width_minus_one
|
||||
total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2
|
||||
|
||||
|
@ -97,7 +97,6 @@ Cura.MachineAction
|
||||
text: Cura.MachineManager.activeMachine.name
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font: UM.Theme.getFont("large_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
|
@ -58,5 +58,5 @@ Button {
|
||||
checkable: true
|
||||
checked: definition.expanded
|
||||
|
||||
onClicked: definition.expanded ? settingDefinitionsModel.collapse(definition.key) : settingDefinitionsModel.expandRecursive(definition.key)
|
||||
onClicked: definition.expanded ? settingDefinitionsModel.collapseRecursive(definition.key) : settingDefinitionsModel.expandRecursive(definition.key)
|
||||
}
|
||||
|
@ -40,11 +40,13 @@ Item
|
||||
// update active type label
|
||||
for (var button in meshTypeButtons.children)
|
||||
{
|
||||
if (meshTypeButtons.children[button].checked){
|
||||
if (meshTypeButtons.children[button].checked)
|
||||
{
|
||||
meshTypeLabel.text = catalog.i18nc("@label", "Mesh Type") + ": " + meshTypeButtons.children[button].text
|
||||
break
|
||||
}
|
||||
}
|
||||
visibility_handler.addSkipResetSetting(currentMeshType)
|
||||
}
|
||||
|
||||
function setOverhangsMeshType()
|
||||
@ -129,7 +131,7 @@ Item
|
||||
|
||||
}
|
||||
|
||||
Label
|
||||
Label
|
||||
{
|
||||
id: meshTypeLabel
|
||||
font: UM.Theme.getFont("default")
|
||||
@ -203,6 +205,7 @@ Item
|
||||
|
||||
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
|
||||
{
|
||||
id: visibility_handler
|
||||
selectedObjectId: UM.ActiveTool.properties.getValue("SelectedObjectId")
|
||||
}
|
||||
|
||||
@ -319,10 +322,7 @@ Item
|
||||
Connections
|
||||
{
|
||||
target: inheritStackProvider
|
||||
onPropertiesChanged:
|
||||
{
|
||||
provider.forcePropertiesChanged()
|
||||
}
|
||||
onPropertiesChanged: provider.forcePropertiesChanged()
|
||||
}
|
||||
|
||||
Connections
|
||||
@ -458,5 +458,4 @@ Item
|
||||
|
||||
Cura.SettingUnknown { }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,133 +7,129 @@ import Cura 1.0 as Cura
|
||||
import ".."
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: settingPickDialog
|
||||
|
||||
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
|
||||
width: screenScaleFactor * 360
|
||||
|
||||
property var additional_excluded_settings
|
||||
|
||||
onVisibilityChanged:
|
||||
{
|
||||
id: settingPickDialog
|
||||
|
||||
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
|
||||
width: screenScaleFactor * 360
|
||||
|
||||
property var additional_excluded_settings
|
||||
|
||||
onVisibilityChanged:
|
||||
// force updating the model to sync it with addedSettingsModel
|
||||
if (visible)
|
||||
{
|
||||
// force updating the model to sync it with addedSettingsModel
|
||||
if (visible)
|
||||
{
|
||||
// Set skip setting, it will prevent from resetting selected mesh_type
|
||||
contents.model.visibilityHandler.addSkipResetSetting(currentMeshType)
|
||||
listview.model.forceUpdate()
|
||||
listview.model.forceUpdate()
|
||||
updateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
updateFilter()
|
||||
}
|
||||
function updateFilter()
|
||||
{
|
||||
var new_filter = {}
|
||||
new_filter["settable_per_mesh"] = true
|
||||
// Don't filter on "settable_per_meshgroup" any more when `printSequencePropertyProvider.properties.value`
|
||||
// is set to "one_at_a_time", because the current backend architecture isn't ready for that.
|
||||
|
||||
if (filterInput.text != "")
|
||||
{
|
||||
new_filter["i18n_label"] = "*" + filterInput.text
|
||||
}
|
||||
|
||||
function updateFilter()
|
||||
listview.model.filter = new_filter
|
||||
}
|
||||
|
||||
TextField
|
||||
{
|
||||
id: filterInput
|
||||
|
||||
anchors
|
||||
{
|
||||
var new_filter = {}
|
||||
new_filter["settable_per_mesh"] = true
|
||||
// Don't filter on "settable_per_meshgroup" any more when `printSequencePropertyProvider.properties.value`
|
||||
// is set to "one_at_a_time", because the current backend architecture isn't ready for that.
|
||||
|
||||
if (filterInput.text != "")
|
||||
{
|
||||
new_filter["i18n_label"] = "*" + filterInput.text
|
||||
}
|
||||
|
||||
listview.model.filter = new_filter
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: toggleShowAll.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: filterInput
|
||||
placeholderText: catalog.i18nc("@label:textbox", "Filter...")
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: toggleShowAll.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
onTextChanged: settingPickDialog.updateFilter()
|
||||
}
|
||||
|
||||
placeholderText: catalog.i18nc("@label:textbox", "Filter...")
|
||||
CheckBox
|
||||
{
|
||||
id: toggleShowAll
|
||||
|
||||
onTextChanged: settingPickDialog.updateFilter()
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
CheckBox
|
||||
text: catalog.i18nc("@label:checkbox", "Show all")
|
||||
checked: listview.model.showAll
|
||||
onClicked: listview.model.showAll = checked
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: scrollView
|
||||
|
||||
anchors
|
||||
{
|
||||
id: toggleShowAll
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: catalog.i18nc("@label:checkbox", "Show all")
|
||||
checked: listview.model.showAll
|
||||
onClicked:
|
||||
{
|
||||
listview.model.showAll = checked
|
||||
}
|
||||
top: filterInput.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
ScrollView
|
||||
ListView
|
||||
{
|
||||
id: scrollView
|
||||
|
||||
anchors
|
||||
id:listview
|
||||
model: UM.SettingDefinitionsModel
|
||||
{
|
||||
top: filterInput.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
ListView
|
||||
{
|
||||
id:listview
|
||||
model: UM.SettingDefinitionsModel
|
||||
id: definitionsModel
|
||||
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
|
||||
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
|
||||
expanded: [ "*" ]
|
||||
exclude:
|
||||
{
|
||||
id: definitionsModel
|
||||
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
|
||||
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
|
||||
expanded: [ "*" ]
|
||||
exclude:
|
||||
var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
|
||||
excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings)
|
||||
return excluded_settings
|
||||
}
|
||||
}
|
||||
delegate:Loader
|
||||
{
|
||||
id: loader
|
||||
|
||||
width: parent.width
|
||||
height: model.type != undefined ? UM.Theme.getSize("section").height : 0
|
||||
|
||||
property var definition: model
|
||||
property var settingDefinitionsModel: definitionsModel
|
||||
|
||||
asynchronous: true
|
||||
source:
|
||||
{
|
||||
switch(model.type)
|
||||
{
|
||||
var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
|
||||
excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings)
|
||||
return excluded_settings
|
||||
case "category":
|
||||
return "PerObjectCategory.qml"
|
||||
default:
|
||||
return "PerObjectItem.qml"
|
||||
}
|
||||
}
|
||||
delegate:Loader
|
||||
{
|
||||
id: loader
|
||||
|
||||
width: parent.width
|
||||
height: model.type != undefined ? UM.Theme.getSize("section").height : 0
|
||||
|
||||
property var definition: model
|
||||
property var settingDefinitionsModel: definitionsModel
|
||||
|
||||
asynchronous: true
|
||||
source:
|
||||
{
|
||||
switch(model.type)
|
||||
{
|
||||
case "category":
|
||||
return "PerObjectCategory.qml"
|
||||
default:
|
||||
return "PerObjectItem.qml"
|
||||
}
|
||||
}
|
||||
}
|
||||
Component.onCompleted: settingPickDialog.updateFilter()
|
||||
}
|
||||
Component.onCompleted: settingPickDialog.updateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
rightButtons: [
|
||||
Button {
|
||||
text: catalog.i18nc("@action:button", "Close")
|
||||
onClicked: {
|
||||
settingPickDialog.visible = false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
rightButtons: [
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Close")
|
||||
onClicked: settingPickDialog.visible = false
|
||||
}
|
||||
]
|
||||
}
|
@ -44,6 +44,9 @@ class PostProcessingPlugin(QObject, Extension):
|
||||
# There can be duplicates, which will be executed in sequence.
|
||||
self._script_list = [] # type: List[Script]
|
||||
self._selected_script_index = -1
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.metaDataChanged.connect(self._restoreScriptInforFromMetadata)
|
||||
|
||||
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) # When the current printer changes, update the list of scripts.
|
||||
@ -209,33 +212,34 @@ class PostProcessingPlugin(QObject, Extension):
|
||||
self.scriptListChanged.emit()
|
||||
self._propertyChanged()
|
||||
|
||||
## When the global container stack is changed, swap out the list of active
|
||||
# scripts.
|
||||
def _onGlobalContainerStackChanged(self) -> None:
|
||||
def _restoreScriptInforFromMetadata(self):
|
||||
self.loadAllScripts()
|
||||
new_stack = Application.getInstance().getGlobalContainerStack()
|
||||
new_stack = self._global_container_stack
|
||||
if new_stack is None:
|
||||
return
|
||||
self._script_list.clear()
|
||||
if not new_stack.getMetaDataEntry("post_processing_scripts"): # Missing or empty.
|
||||
self.scriptListChanged.emit() # Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
|
||||
if not new_stack.getMetaDataEntry("post_processing_scripts"): # Missing or empty.
|
||||
self.scriptListChanged.emit() # Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
|
||||
self.setSelectedScriptIndex(-1)
|
||||
return
|
||||
|
||||
self._script_list.clear()
|
||||
scripts_list_strs = new_stack.getMetaDataEntry("post_processing_scripts")
|
||||
for script_str in scripts_list_strs.split("\n"): # Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
|
||||
for script_str in scripts_list_strs.split(
|
||||
"\n"): # Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
|
||||
if not script_str: # There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
|
||||
continue
|
||||
script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") # Unescape escape sequences.
|
||||
script_parser = configparser.ConfigParser(interpolation = None)
|
||||
script_parser = configparser.ConfigParser(interpolation=None)
|
||||
script_parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive.
|
||||
script_parser.read_string(script_str)
|
||||
for script_name, settings in script_parser.items(): # There should only be one, really! Otherwise we can't guarantee the order or allow multiple uses of the same script.
|
||||
if script_name == "DEFAULT": # ConfigParser always has a DEFAULT section, but we don't fill it. Ignore this one.
|
||||
continue
|
||||
if script_name not in self._loaded_scripts: # Don't know this post-processing plug-in.
|
||||
Logger.log("e", "Unknown post-processing script {script_name} was encountered in this global stack.".format(script_name = script_name))
|
||||
if script_name not in self._loaded_scripts: # Don't know this post-processing plug-in.
|
||||
Logger.log("e",
|
||||
"Unknown post-processing script {script_name} was encountered in this global stack.".format(
|
||||
script_name=script_name))
|
||||
continue
|
||||
new_script = self._loaded_scripts[script_name]()
|
||||
new_script.initialize()
|
||||
@ -245,7 +249,22 @@ class PostProcessingPlugin(QObject, Extension):
|
||||
self._script_list.append(new_script)
|
||||
|
||||
self.setSelectedScriptIndex(0)
|
||||
# Ensure that we always force an update (otherwise the fields don't update correctly!)
|
||||
self.selectedIndexChanged.emit()
|
||||
self.scriptListChanged.emit()
|
||||
self._propertyChanged()
|
||||
|
||||
## When the global container stack is changed, swap out the list of active
|
||||
# scripts.
|
||||
def _onGlobalContainerStackChanged(self) -> None:
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.metaDataChanged.disconnect(self._restoreScriptInforFromMetadata)
|
||||
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.metaDataChanged.connect(self._restoreScriptInforFromMetadata)
|
||||
self._restoreScriptInforFromMetadata()
|
||||
|
||||
@pyqtSlot()
|
||||
def writeScriptsToStack(self) -> None:
|
||||
@ -267,14 +286,18 @@ class PostProcessingPlugin(QObject, Extension):
|
||||
|
||||
script_list_string = "\n".join(script_list_strs) # ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
|
||||
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
|
||||
if "post_processing_scripts" not in global_stack.getMetaData():
|
||||
global_stack.setMetaDataEntry("post_processing_scripts", "")
|
||||
# Ensure we don't get triggered by our own write.
|
||||
self._global_container_stack.metaDataChanged.disconnect(self._restoreScriptInforFromMetadata)
|
||||
|
||||
global_stack.setMetaDataEntry("post_processing_scripts", script_list_string)
|
||||
if "post_processing_scripts" not in self._global_container_stack.getMetaData():
|
||||
self._global_container_stack.setMetaDataEntry("post_processing_scripts", "")
|
||||
|
||||
self._global_container_stack.setMetaDataEntry("post_processing_scripts", script_list_string)
|
||||
# We do want to listen to other events.
|
||||
self._global_container_stack.metaDataChanged.connect(self._restoreScriptInforFromMetadata)
|
||||
|
||||
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
|
||||
def _createView(self) -> None:
|
||||
|
58
plugins/SentryLogger/SentryLogger.py
Normal file
58
plugins/SentryLogger/SentryLogger.py
Normal file
@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import LogOutput
|
||||
from typing import Set
|
||||
try:
|
||||
from sentry_sdk import add_breadcrumb
|
||||
except ImportError:
|
||||
pass
|
||||
from typing import Optional
|
||||
import os
|
||||
|
||||
home_dir = os.path.expanduser("~")
|
||||
|
||||
|
||||
class SentryLogger(LogOutput):
|
||||
# Sentry (https://sentry.io) is the service that Cura uses for logging crashes. This logger ensures that the
|
||||
# regular log entries that we create are added as breadcrumbs so when a crash actually happens, they are already
|
||||
# processed and ready for sending.
|
||||
# Note that this only prepares them for sending. It only sends them when the user actually agrees to sending the
|
||||
# information.
|
||||
|
||||
_levels = {
|
||||
"w": "warning",
|
||||
"i": "info",
|
||||
"c": "fatal",
|
||||
"e": "error",
|
||||
"d": "debug"
|
||||
}
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._show_once = set() # type: Set[str]
|
||||
|
||||
## Log the message to the sentry hub as a breadcrumb
|
||||
# \param log_type "e" (error), "i"(info), "d"(debug), "w"(warning) or "c"(critical) (can postfix with "_once")
|
||||
# \param message String containing message to be logged
|
||||
def log(self, log_type: str, message: str) -> None:
|
||||
level = self._translateLogType(log_type)
|
||||
message = self._pruneSensitiveData(message)
|
||||
if level is None:
|
||||
if message not in self._show_once:
|
||||
level = self._translateLogType(log_type[0])
|
||||
if level is not None:
|
||||
self._show_once.add(message)
|
||||
add_breadcrumb(level = level, message = message)
|
||||
else:
|
||||
add_breadcrumb(level = level, message = message)
|
||||
|
||||
@staticmethod
|
||||
def _pruneSensitiveData(message):
|
||||
if home_dir in message:
|
||||
message = message.replace(home_dir, "<user_home>")
|
||||
return message
|
||||
|
||||
@staticmethod
|
||||
def _translateLogType(log_type: str) -> Optional[str]:
|
||||
return SentryLogger._levels.get(log_type)
|
23
plugins/SentryLogger/__init__.py
Normal file
23
plugins/SentryLogger/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import TYPE_CHECKING, Dict, Any
|
||||
try:
|
||||
import sentry_sdk
|
||||
has_sentry = True
|
||||
except ImportError:
|
||||
has_sentry = False
|
||||
|
||||
from . import SentryLogger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Application import Application
|
||||
|
||||
|
||||
def getMetaData() -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
|
||||
def register(app: "Application") -> Dict[str, Any]:
|
||||
if not has_sentry:
|
||||
return {} # Nothing to do here!
|
||||
return {"logger": SentryLogger.SentryLogger()}
|
8
plugins/SentryLogger/plugin.json
Normal file
8
plugins/SentryLogger/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Sentry Logger",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Logs certain events so that they can be used by the crash reporter",
|
||||
"api": "7.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -46,19 +46,10 @@ class SimulationPass(RenderPass):
|
||||
self._layer_view = layerview
|
||||
self._compatibility_mode = layerview.getCompatibilityMode()
|
||||
|
||||
def render(self):
|
||||
if not self._layer_shader:
|
||||
if self._compatibility_mode:
|
||||
shader_filename = "layers.shader"
|
||||
shadow_shader_filename = "layers_shadow.shader"
|
||||
else:
|
||||
shader_filename = "layers3d.shader"
|
||||
shadow_shader_filename = "layers3d_shadow.shader"
|
||||
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shader_filename))
|
||||
self._layer_shadow_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shadow_shader_filename))
|
||||
self._current_shader = self._layer_shader
|
||||
def _updateLayerShaderValues(self):
|
||||
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
|
||||
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
|
||||
self._layer_shader.setUniformValue("u_active_extruder",
|
||||
float(max(0, self._extruder_manager.activeExtruderIndex)))
|
||||
if self._layer_view:
|
||||
self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
|
||||
self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
|
||||
@ -71,7 +62,7 @@ class SimulationPass(RenderPass):
|
||||
self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
|
||||
self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
|
||||
else:
|
||||
#defaults
|
||||
# defaults
|
||||
self._layer_shader.setUniformValue("u_max_feedrate", 1)
|
||||
self._layer_shader.setUniformValue("u_min_feedrate", 0)
|
||||
self._layer_shader.setUniformValue("u_max_thickness", 1)
|
||||
@ -83,6 +74,20 @@ class SimulationPass(RenderPass):
|
||||
self._layer_shader.setUniformValue("u_show_skin", 1)
|
||||
self._layer_shader.setUniformValue("u_show_infill", 1)
|
||||
|
||||
def render(self):
|
||||
if not self._layer_shader:
|
||||
if self._compatibility_mode:
|
||||
shader_filename = "layers.shader"
|
||||
shadow_shader_filename = "layers_shadow.shader"
|
||||
else:
|
||||
shader_filename = "layers3d.shader"
|
||||
shadow_shader_filename = "layers3d_shadow.shader"
|
||||
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shader_filename))
|
||||
self._layer_shadow_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shadow_shader_filename))
|
||||
self._current_shader = self._layer_shader
|
||||
|
||||
self._updateLayerShaderValues()
|
||||
|
||||
if not self._tool_handle_shader:
|
||||
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
|
||||
|
||||
@ -93,12 +98,10 @@ class SimulationPass(RenderPass):
|
||||
self.bind()
|
||||
|
||||
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
head_position = None # Indicates the current position of the print head
|
||||
nozzle_node = None
|
||||
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
|
||||
if isinstance(node, ToolHandle):
|
||||
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
|
||||
|
||||
@ -113,29 +116,24 @@ class SimulationPass(RenderPass):
|
||||
|
||||
# Render all layers below a certain number as line mesh instead of vertices.
|
||||
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
|
||||
start = 0
|
||||
end = 0
|
||||
element_counts = layer_data.getElementCounts()
|
||||
for layer in sorted(element_counts.keys()):
|
||||
# In the current layer, we show just the indicated paths
|
||||
if layer == self._layer_view._current_layer_num:
|
||||
# We look for the position of the head, searching the point of the current path
|
||||
index = self._layer_view._current_path_num
|
||||
offset = 0
|
||||
for polygon in layer_data.getLayer(layer).polygons:
|
||||
# The size indicates all values in the two-dimension array, and the second dimension is
|
||||
# always size 3 because we have 3D points.
|
||||
if index >= polygon.data.size // 3 - offset:
|
||||
index -= polygon.data.size // 3 - offset
|
||||
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
|
||||
continue
|
||||
# The head position is calculated and translated
|
||||
head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition()
|
||||
break
|
||||
break
|
||||
if self._layer_view._minimum_layer_num > layer:
|
||||
start += element_counts[layer]
|
||||
end += element_counts[layer]
|
||||
start = self._layer_view.start_elements_index
|
||||
end = self._layer_view.end_elements_index
|
||||
index = self._layer_view._current_path_num
|
||||
offset = 0
|
||||
layer = layer_data.getLayer(self._layer_view._current_layer_num)
|
||||
if layer is None:
|
||||
continue
|
||||
for polygon in layer.polygons:
|
||||
# The size indicates all values in the two-dimension array, and the second dimension is
|
||||
# always size 3 because we have 3D points.
|
||||
if index >= polygon.data.size // 3 - offset:
|
||||
index -= polygon.data.size // 3 - offset
|
||||
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
|
||||
continue
|
||||
# The head position is calculated and translated
|
||||
head_position = Vector(polygon.data[index + offset][0], polygon.data[index + offset][1],
|
||||
polygon.data[index + offset][2]) + node.getWorldPosition()
|
||||
break
|
||||
|
||||
# Calculate the range of paths in the last layer
|
||||
current_layer_start = end
|
||||
|
@ -71,6 +71,8 @@ class SimulationView(CuraView):
|
||||
self._max_paths = 0
|
||||
self._current_path_num = 0
|
||||
self._minimum_path_num = 0
|
||||
self.start_elements_index = 0
|
||||
self.end_elements_index = 0
|
||||
self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
|
||||
|
||||
self._busy = False
|
||||
@ -243,6 +245,7 @@ class SimulationView(CuraView):
|
||||
self._minimum_layer_num = self._current_layer_num
|
||||
|
||||
self._startUpdateTopLayers()
|
||||
self.recalculateStartEndElements()
|
||||
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
@ -257,7 +260,7 @@ class SimulationView(CuraView):
|
||||
self._current_layer_num = self._minimum_layer_num
|
||||
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
self.recalculateStartEndElements()
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
def setPath(self, value: int) -> None:
|
||||
@ -271,7 +274,7 @@ class SimulationView(CuraView):
|
||||
self._minimum_path_num = self._current_path_num
|
||||
|
||||
self._startUpdateTopLayers()
|
||||
|
||||
self.recalculateStartEndElements()
|
||||
self.currentPathNumChanged.emit()
|
||||
|
||||
def setMinimumPath(self, value: int) -> None:
|
||||
@ -292,8 +295,9 @@ class SimulationView(CuraView):
|
||||
#
|
||||
# \param layer_view_type integer as in SimulationView.qml and this class
|
||||
def setSimulationViewType(self, layer_view_type: int) -> None:
|
||||
self._layer_view_type = layer_view_type
|
||||
self.currentLayerNumChanged.emit()
|
||||
if layer_view_type != self._layer_view_type:
|
||||
self._layer_view_type = layer_view_type
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
## Return the layer view type, integer as in SimulationView.qml and this class
|
||||
def getSimulationViewType(self) -> int:
|
||||
@ -358,6 +362,24 @@ class SimulationView(CuraView):
|
||||
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
||||
return self._min_thickness
|
||||
|
||||
def recalculateStartEndElements(self):
|
||||
self.start_elements_index = 0
|
||||
self.end_elements_index = 0
|
||||
scene = self.getController().getScene()
|
||||
for node in DepthFirstIterator(scene.getRoot()): # type: ignore
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
||||
# Found a the layer data!
|
||||
element_counts = layer_data.getElementCounts()
|
||||
for layer in sorted(element_counts.keys()):
|
||||
if layer == self._current_layer_num:
|
||||
break
|
||||
if self._minimum_layer_num > layer:
|
||||
self.start_elements_index += element_counts[layer]
|
||||
self.end_elements_index += element_counts[layer]
|
||||
|
||||
def getMaxThickness(self) -> float:
|
||||
return self._max_thickness
|
||||
|
||||
@ -571,11 +593,13 @@ class SimulationView(CuraView):
|
||||
|
||||
def _onCurrentLayerNumChanged(self) -> None:
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
scene = Application.getInstance().getController().getScene()
|
||||
scene.sceneChanged.emit(scene.getRoot())
|
||||
|
||||
def _startUpdateTopLayers(self) -> None:
|
||||
if not self._compatibility_mode:
|
||||
return
|
||||
|
||||
self.recalculateStartEndElements()
|
||||
if self._top_layers_job:
|
||||
self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
|
||||
self._top_layers_job.cancel()
|
||||
|
@ -149,6 +149,9 @@ class SimulationViewProxy(QObject):
|
||||
self.currentPathChanged.emit()
|
||||
self._layerActivityChanged()
|
||||
|
||||
scene = Application.getInstance().getController().getScene()
|
||||
scene.sceneChanged.emit(scene.getRoot())
|
||||
|
||||
def _onMaxLayersChanged(self):
|
||||
self.maxLayersChanged.emit()
|
||||
|
||||
|
@ -88,7 +88,7 @@ Window
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
textArea.text: manager.getExampleData()
|
||||
textArea.text: (manager === null) ? "" : manager.getExampleData()
|
||||
textArea.textFormat: Text.RichText
|
||||
textArea.wrapMode: Text.Wrap
|
||||
textArea.readOnly: true
|
||||
|
@ -5,14 +5,13 @@ import json
|
||||
import os
|
||||
import platform
|
||||
import time
|
||||
from typing import cast, Optional, Set
|
||||
from typing import cast, Optional, Set, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Application import Application
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Message import Message
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
@ -20,7 +19,8 @@ from UM.Qt.Duration import DurationFormat
|
||||
|
||||
from cura import ApplicationMetadata
|
||||
|
||||
from .SliceInfoJob import SliceInfoJob
|
||||
if TYPE_CHECKING:
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
@ -36,7 +36,8 @@ class SliceInfo(QObject, Extension):
|
||||
QObject.__init__(self, parent)
|
||||
Extension.__init__(self)
|
||||
|
||||
self._application = Application.getInstance()
|
||||
from cura.CuraApplication import CuraApplication
|
||||
self._application = CuraApplication.getInstance()
|
||||
|
||||
self._application.getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
|
||||
self._application.getPreferences().addPreference("info/send_slice_info", True)
|
||||
@ -56,7 +57,7 @@ class SliceInfo(QObject, Extension):
|
||||
## Perform action based on user input.
|
||||
# Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it.
|
||||
def messageActionTriggered(self, message_id, action_id):
|
||||
Application.getInstance().getPreferences().setValue("info/asked_send_slice_info", True)
|
||||
self._application.getPreferences().setValue("info/asked_send_slice_info", True)
|
||||
if action_id == "MoreInfo":
|
||||
self.showMoreInfoDialog()
|
||||
self.send_slice_info_message.hide()
|
||||
@ -69,7 +70,7 @@ class SliceInfo(QObject, Extension):
|
||||
def _createDialog(self, qml_name):
|
||||
Logger.log("d", "Creating dialog [%s]", qml_name)
|
||||
file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name)
|
||||
dialog = Application.getInstance().createQmlComponent(file_path, {"manager": self})
|
||||
dialog = self._application.createQmlComponent(file_path, {"manager": self})
|
||||
return dialog
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
@ -87,12 +88,10 @@ class SliceInfo(QObject, Extension):
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def setSendSliceInfo(self, enabled: bool):
|
||||
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
|
||||
self._application.getPreferences().setValue("info/send_slice_info", enabled)
|
||||
|
||||
def _getUserModifiedSettingKeys(self) -> list:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = cast(CuraApplication, Application.getInstance())
|
||||
machine_manager = application.getMachineManager()
|
||||
machine_manager = self._application.getMachineManager()
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
user_modified_setting_keys = set() # type: Set[str]
|
||||
@ -106,30 +105,28 @@ class SliceInfo(QObject, Extension):
|
||||
|
||||
def _onWriteStarted(self, output_device):
|
||||
try:
|
||||
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
|
||||
if not self._application.getPreferences().getValue("info/send_slice_info"):
|
||||
Logger.log("d", "'info/send_slice_info' is turned off.")
|
||||
return # Do nothing, user does not want to send data
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = cast(CuraApplication, Application.getInstance())
|
||||
machine_manager = application.getMachineManager()
|
||||
print_information = application.getPrintInformation()
|
||||
machine_manager = self._application.getMachineManager()
|
||||
print_information = self._application.getPrintInformation()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
data = dict() # The data that we're going to submit.
|
||||
data["time_stamp"] = time.time()
|
||||
data["schema_version"] = 0
|
||||
data["cura_version"] = application.getVersion()
|
||||
data["cura_version"] = self._application.getVersion()
|
||||
data["cura_build_type"] = ApplicationMetadata.CuraBuildType
|
||||
|
||||
active_mode = Application.getInstance().getPreferences().getValue("cura/active_mode")
|
||||
active_mode = self._application.getPreferences().getValue("cura/active_mode")
|
||||
if active_mode == 0:
|
||||
data["active_mode"] = "recommended"
|
||||
else:
|
||||
data["active_mode"] = "custom"
|
||||
|
||||
data["camera_view"] = application.getPreferences().getValue("general/camera_perspective_mode")
|
||||
data["camera_view"] = self._application.getPreferences().getValue("general/camera_perspective_mode")
|
||||
if data["camera_view"] == "orthographic":
|
||||
data["camera_view"] = "orthogonal" #The database still only recognises the old name "orthogonal".
|
||||
|
||||
@ -142,7 +139,7 @@ class SliceInfo(QObject, Extension):
|
||||
machine_settings_changed_by_user = True
|
||||
|
||||
data["machine_settings_changed_by_user"] = machine_settings_changed_by_user
|
||||
data["language"] = Application.getInstance().getPreferences().getValue("general/language")
|
||||
data["language"] = self._application.getPreferences().getValue("general/language")
|
||||
data["os"] = {"type": platform.system(), "version": platform.version()}
|
||||
|
||||
data["active_machine"] = {"definition_id": global_stack.definition.getId(),
|
||||
@ -184,7 +181,7 @@ class SliceInfo(QObject, Extension):
|
||||
|
||||
data["models"] = []
|
||||
# Listing all files placed on the build plate
|
||||
for node in DepthFirstIterator(application.getController().getScene().getRoot()):
|
||||
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()):
|
||||
if node.callDecoration("isSliceable"):
|
||||
model = dict()
|
||||
model["hash"] = node.getMeshData().getHash()
|
||||
@ -263,10 +260,23 @@ class SliceInfo(QObject, Extension):
|
||||
# Convert data to bytes
|
||||
binary_data = json.dumps(data).encode("utf-8")
|
||||
|
||||
# Sending slice info non-blocking
|
||||
reportJob = SliceInfoJob(self.info_url, binary_data)
|
||||
reportJob.start()
|
||||
# Send slice info non-blocking
|
||||
network_manager = self._application.getHttpRequestManager()
|
||||
network_manager.post(self.info_url, data = binary_data,
|
||||
callback = self._onRequestFinished, error_callback = self._onRequestError)
|
||||
except Exception:
|
||||
# We really can't afford to have a mistake here, as this would break the sending of g-code to a device
|
||||
# (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
|
||||
Logger.logException("e", "Exception raised while sending slice info.") # But we should be notified about these problems of course.
|
||||
|
||||
def _onRequestFinished(self, reply: "QNetworkReply") -> None:
|
||||
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||
if status_code == 200:
|
||||
Logger.log("i", "SliceInfo sent successfully")
|
||||
return
|
||||
|
||||
data = reply.readAll().data().decode("utf-8")
|
||||
Logger.log("e", "SliceInfo request failed, status code %s, data: %s", status_code, data)
|
||||
|
||||
def _onRequestError(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||
Logger.log("e", "Got error for SliceInfo request: %s", reply.errorString())
|
||||
|
@ -1,43 +0,0 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Platform import Platform
|
||||
|
||||
import ssl
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
import certifi
|
||||
|
||||
|
||||
class SliceInfoJob(Job):
|
||||
def __init__(self, url, data):
|
||||
super().__init__()
|
||||
self._url = url
|
||||
self._data = data
|
||||
|
||||
def run(self):
|
||||
if not self._url or not self._data:
|
||||
Logger.log("e", "URL or DATA for sending slice info was not set!")
|
||||
return
|
||||
|
||||
# CURA-6698 Create an SSL context and use certifi CA certificates for verification.
|
||||
context = ssl.SSLContext(protocol = ssl.PROTOCOL_TLSv1_2)
|
||||
context.load_verify_locations(cafile = certifi.where())
|
||||
|
||||
# Submit data
|
||||
kwoptions = {"data": self._data,
|
||||
"timeout": 5,
|
||||
"context": context}
|
||||
|
||||
Logger.log("i", "Sending anonymous slice info to [%s]...", self._url)
|
||||
|
||||
try:
|
||||
f = urllib.request.urlopen(self._url, **kwoptions)
|
||||
Logger.log("i", "Sent anonymous slice info.")
|
||||
f.close()
|
||||
except urllib.error.HTTPError:
|
||||
Logger.logException("e", "An HTTP error occurred while trying to send slice information")
|
||||
except Exception: # We don't want any exception to cause problems
|
||||
Logger.logException("e", "An exception occurred while trying to send slice information")
|
@ -65,10 +65,7 @@ class SolidView(View):
|
||||
else:
|
||||
self._support_angle = support_angle_stack.getProperty("support_angle", "value")
|
||||
|
||||
def beginRendering(self):
|
||||
scene = self.getController().getScene()
|
||||
renderer = self.getRenderer()
|
||||
|
||||
def _checkSetup(self):
|
||||
if not self._extruders_model:
|
||||
self._extruders_model = Application.getInstance().getExtrudersModel()
|
||||
|
||||
@ -95,6 +92,12 @@ class SolidView(View):
|
||||
self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
|
||||
self._support_mesh_shader.setUniformValue("u_width", 5.0)
|
||||
|
||||
def beginRendering(self):
|
||||
scene = self.getController().getScene()
|
||||
renderer = self.getRenderer()
|
||||
|
||||
self._checkSetup()
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
if Application.getInstance().getPreferences().getValue("view/show_overhang"):
|
||||
|
144
plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml
Normal file
144
plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.6 as Cura
|
||||
|
||||
|
||||
UM.Dialog{
|
||||
visible: true
|
||||
title: catalog.i18nc("@title", "Changes from your account")
|
||||
width: UM.Theme.getSize("popup_dialog").width
|
||||
height: UM.Theme.getSize("popup_dialog").height
|
||||
minimumWidth: width
|
||||
maximumWidth: minimumWidth
|
||||
minimumHeight: height
|
||||
maximumHeight: minimumHeight
|
||||
margin: 0
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
color: UM.Theme.getColor("main_background")
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
width: parent.width
|
||||
height: parent.height - nextButton.height - nextButton.anchors.margins * 2 // We want some leftover space for the button at the bottom
|
||||
clip: true
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
// Compatible packages
|
||||
Label
|
||||
{
|
||||
font: UM.Theme.getFont("default")
|
||||
text: catalog.i18nc("@label", "The following packages will be added:")
|
||||
visible: toolbox.has_compatible_packages
|
||||
color: UM.Theme.getColor("text")
|
||||
height: contentHeight + UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
model: toolbox.subscribedPackagesModel
|
||||
Component
|
||||
{
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
property int lineHeight: 60
|
||||
visible: model.is_compatible
|
||||
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the compatible packages here
|
||||
Image
|
||||
{
|
||||
id: packageIcon
|
||||
source: model.icon_url || "../../images/logobot.svg"
|
||||
height: lineHeight
|
||||
width: height
|
||||
mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: model.name
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
anchors.left: packageIcon.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: packageIcon.verticalCenter
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Incompatible packages
|
||||
Label
|
||||
{
|
||||
font: UM.Theme.getFont("default")
|
||||
text: catalog.i18nc("@label", "The following packages can not be installed because of incompatible Cura version:")
|
||||
visible: toolbox.has_incompatible_packages
|
||||
color: UM.Theme.getColor("text")
|
||||
height: contentHeight + UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
Repeater
|
||||
{
|
||||
model: toolbox.subscribedPackagesModel
|
||||
Component
|
||||
{
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
property int lineHeight: 60
|
||||
visible: !model.is_compatible
|
||||
height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here
|
||||
Image
|
||||
{
|
||||
id: packageIcon
|
||||
source: model.icon_url || "../../images/logobot.svg"
|
||||
height: lineHeight
|
||||
width: height
|
||||
mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: model.name
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
anchors.left: packageIcon.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: packageIcon.verticalCenter
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of ScrollView
|
||||
|
||||
Cura.ActionButton
|
||||
{
|
||||
id: nextButton
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
text: catalog.i18nc("@button", "Next")
|
||||
}
|
||||
}
|
||||
}
|
@ -57,6 +57,7 @@ UM.Dialog
|
||||
{
|
||||
licenseDialog.close();
|
||||
toolbox.install(licenseDialog.pluginFileLocation);
|
||||
toolbox.subscribe(licenseDialog.pluginName);
|
||||
}
|
||||
},
|
||||
Button
|
||||
|
61
plugins/Toolbox/src/SubscribedPackagesModel.py
Normal file
61
plugins/Toolbox/src/SubscribedPackagesModel.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Copyright (c) 2020 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from cura import ApplicationMetadata
|
||||
|
||||
|
||||
class SubscribedPackagesModel(ListModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._items = []
|
||||
self._metadata = None
|
||||
self._discrepancies = None
|
||||
self._sdk_version = ApplicationMetadata.CuraSDKVersion
|
||||
|
||||
self.addRoleName(Qt.UserRole + 1, "name")
|
||||
self.addRoleName(Qt.UserRole + 2, "icon_url")
|
||||
self.addRoleName(Qt.UserRole + 3, "is_compatible")
|
||||
|
||||
def setMetadata(self, data):
|
||||
if self._metadata != data:
|
||||
self._metadata = data
|
||||
|
||||
def addValue(self, discrepancy):
|
||||
if self._discrepancies != discrepancy:
|
||||
self._discrepancies = discrepancy
|
||||
|
||||
def update(self):
|
||||
self._items.clear()
|
||||
|
||||
for item in self._metadata:
|
||||
if item["package_id"] not in self._discrepancies:
|
||||
continue
|
||||
package = {"name": item["display_name"], "sdk_versions": item["sdk_versions"]}
|
||||
if self._sdk_version not in item["sdk_versions"]:
|
||||
package.update({"is_compatible": False})
|
||||
else:
|
||||
package.update({"is_compatible": True})
|
||||
try:
|
||||
package.update({"icon_url": item["icon_url"]})
|
||||
except KeyError: # There is no 'icon_url" in the response payload for this package
|
||||
package.update({"icon_url": ""})
|
||||
|
||||
self._items.append(package)
|
||||
self.setItems(self._items)
|
||||
|
||||
def hasCompatiblePackages(self) -> bool:
|
||||
has_compatible_items = False
|
||||
for item in self._items:
|
||||
if item['is_compatible'] == True:
|
||||
has_compatible_items = True
|
||||
return has_compatible_items
|
||||
|
||||
def hasIncompatiblePackages(self) -> bool:
|
||||
has_incompatible_items = False
|
||||
for item in self._items:
|
||||
if item['is_compatible'] == False:
|
||||
has_incompatible_items = True
|
||||
return has_incompatible_items
|
@ -7,7 +7,7 @@ import tempfile
|
||||
import platform
|
||||
from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
|
||||
from UM.Logger import Logger
|
||||
@ -15,6 +15,7 @@ from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Extension import Extension
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Version import Version
|
||||
from UM.Message import Message
|
||||
|
||||
from cura import ApplicationMetadata
|
||||
from cura import UltimakerCloudAuthentication
|
||||
@ -23,8 +24,10 @@ from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
from .AuthorsModel import AuthorsModel
|
||||
from .PackagesModel import PackagesModel
|
||||
from .SubscribedPackagesModel import SubscribedPackagesModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.TaskManagement.HttpRequestData import HttpRequestData
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
@ -43,32 +46,32 @@ class Toolbox(QObject, Extension):
|
||||
self._api_url = None # type: Optional[str]
|
||||
|
||||
# Network:
|
||||
self._download_request = None # type: Optional[QNetworkRequest]
|
||||
self._download_reply = None # type: Optional[QNetworkReply]
|
||||
self._download_request_data = None # type: Optional[HttpRequestData]
|
||||
self._download_progress = 0 # type: float
|
||||
self._is_downloading = False # type: bool
|
||||
self._network_manager = None # type: Optional[QNetworkAccessManager]
|
||||
self._request_headers = [] # type: List[Tuple[bytes, bytes]]
|
||||
self._request_headers = dict() # type: Dict[str, str]
|
||||
self._updateRequestHeader()
|
||||
|
||||
self._request_urls = {} # type: Dict[str, QUrl]
|
||||
self._request_urls = {} # type: Dict[str, str]
|
||||
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
|
||||
self._old_plugin_ids = set() # type: Set[str]
|
||||
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
# The responses as given by the server parsed to a list.
|
||||
self._server_response_data = {
|
||||
"authors": [],
|
||||
"packages": [],
|
||||
"updates": [],
|
||||
"authors": [],
|
||||
"packages": [],
|
||||
"updates": [],
|
||||
"subscribed_packages": [],
|
||||
} # type: Dict[str, List[Any]]
|
||||
|
||||
# Models:
|
||||
self._models = {
|
||||
"authors": AuthorsModel(self),
|
||||
"packages": PackagesModel(self),
|
||||
"updates": PackagesModel(self),
|
||||
} # type: Dict[str, Union[AuthorsModel, PackagesModel]]
|
||||
"authors": AuthorsModel(self),
|
||||
"packages": PackagesModel(self),
|
||||
"updates": PackagesModel(self),
|
||||
"subscribed_packages": SubscribedPackagesModel(self),
|
||||
} # type: Dict[str, Union[AuthorsModel, PackagesModel, SubscribedPackagesModel]]
|
||||
|
||||
self._plugins_showcase_model = PackagesModel(self)
|
||||
self._plugins_available_model = PackagesModel(self)
|
||||
@ -138,20 +141,15 @@ class Toolbox(QObject, Extension):
|
||||
self._fetchPackageData()
|
||||
|
||||
def _updateRequestHeader(self):
|
||||
self._request_headers = [
|
||||
(b"User-Agent",
|
||||
str.encode(
|
||||
"%s/%s (%s %s)" % (
|
||||
self._application.getApplicationName(),
|
||||
self._application.getVersion(),
|
||||
platform.system(),
|
||||
platform.machine(),
|
||||
)
|
||||
))
|
||||
]
|
||||
self._request_headers = {
|
||||
"User-Agent": "%s/%s (%s %s)" % (self._application.getApplicationName(),
|
||||
self._application.getVersion(),
|
||||
platform.system(),
|
||||
platform.machine())
|
||||
}
|
||||
access_token = self._application.getCuraAPI().account.accessToken
|
||||
if access_token:
|
||||
self._request_headers.append((b"Authorization", "Bearer {}".format(access_token).encode()))
|
||||
self._request_headers["Authorization"] = "Bearer {}".format(access_token)
|
||||
|
||||
def _resetUninstallVariables(self) -> None:
|
||||
self._package_id_to_uninstall = None # type: Optional[str]
|
||||
@ -161,13 +159,19 @@ class Toolbox(QObject, Extension):
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
def ratePackage(self, package_id: str, rating: int) -> None:
|
||||
url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url=self._api_url, package_id = package_id))
|
||||
|
||||
self._rate_request = QNetworkRequest(url)
|
||||
for header_name, header_value in self._request_headers:
|
||||
cast(QNetworkRequest, self._rate_request).setRawHeader(header_name, header_value)
|
||||
url = "{base_url}/packages/{package_id}/ratings".format(base_url = self._api_url, package_id = package_id)
|
||||
data = "{\"data\": {\"cura_version\": \"%s\", \"rating\": %i}}" % (Version(self._application.getVersion()), rating)
|
||||
self._rate_reply = cast(QNetworkAccessManager, self._network_manager).put(self._rate_request, data.encode())
|
||||
|
||||
self._application.getHttpRequestManager().put(url, headers_dict = self._request_headers,
|
||||
data = data.encode())
|
||||
@pyqtSlot(str)
|
||||
def subscribe(self, package_id: str) -> None:
|
||||
if self._application.getCuraAPI().account.isLoggedIn:
|
||||
data = "{\"data\": {\"package_id\": \"%s\", \"sdk_version\": \"%s\"}}" % (package_id, self._sdk_version)
|
||||
self._application.getHttpRequestManager().put(url=self._api_url_user_packages,
|
||||
headers_dict=self._request_headers,
|
||||
data=data.encode()
|
||||
)
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getLicenseDialogPluginName(self) -> str:
|
||||
@ -197,6 +201,11 @@ class Toolbox(QObject, Extension):
|
||||
cloud_api_version = self._cloud_api_version,
|
||||
sdk_version = self._sdk_version
|
||||
)
|
||||
# https://api.ultimaker.com/cura-packages/v1/user/packages
|
||||
self._api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}/user/packages".format(
|
||||
cloud_api_root = self._cloud_api_root,
|
||||
cloud_api_version = self._cloud_api_version,
|
||||
)
|
||||
|
||||
# We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc.
|
||||
installed_package_ids_with_versions = [":".join(items) for items in
|
||||
@ -204,43 +213,35 @@ class Toolbox(QObject, Extension):
|
||||
installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions)
|
||||
|
||||
self._request_urls = {
|
||||
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
|
||||
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
|
||||
"updates": QUrl("{base_url}/packages/package-updates?installed_packages={query}".format(
|
||||
base_url = self._api_url, query = installed_packages_query))
|
||||
"authors": "{base_url}/authors".format(base_url = self._api_url),
|
||||
"packages": "{base_url}/packages".format(base_url = self._api_url),
|
||||
"updates": "{base_url}/packages/package-updates?installed_packages={query}".format(
|
||||
base_url = self._api_url, query = installed_packages_query),
|
||||
"subscribed_packages": self._api_url_user_packages,
|
||||
}
|
||||
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._restart)
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._fetchUserSubscribedPackages)
|
||||
|
||||
# On boot we check which packages have updates.
|
||||
if CuraApplication.getInstance().getPreferences().getValue("info/automatic_update_check") and len(installed_package_ids_with_versions) > 0:
|
||||
# Request the latest and greatest!
|
||||
self._fetchPackageUpdates()
|
||||
self._makeRequestByType("updates")
|
||||
self._fetchUserSubscribedPackages()
|
||||
|
||||
def _prepareNetworkManager(self):
|
||||
if self._network_manager is not None:
|
||||
self._network_manager.finished.disconnect(self._onRequestFinished)
|
||||
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged)
|
||||
self._network_manager = QNetworkAccessManager()
|
||||
self._network_manager.finished.connect(self._onRequestFinished)
|
||||
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged)
|
||||
|
||||
def _fetchPackageUpdates(self):
|
||||
self._prepareNetworkManager()
|
||||
self._makeRequestByType("updates")
|
||||
def _fetchUserSubscribedPackages(self):
|
||||
if self._application.getCuraAPI().account.isLoggedIn:
|
||||
self._makeRequestByType("subscribed_packages")
|
||||
|
||||
def _fetchPackageData(self):
|
||||
self._prepareNetworkManager()
|
||||
# Make remote requests:
|
||||
def _fetchPackageData(self) -> None:
|
||||
self._makeRequestByType("packages")
|
||||
self._makeRequestByType("authors")
|
||||
# Gather installed packages:
|
||||
self._updateInstalledModels()
|
||||
|
||||
# Displays the toolbox
|
||||
@pyqtSlot()
|
||||
def launch(self) -> None:
|
||||
|
||||
if not self._dialog:
|
||||
self._dialog = self._createDialog("Toolbox.qml")
|
||||
|
||||
@ -251,7 +252,6 @@ class Toolbox(QObject, Extension):
|
||||
self._restart()
|
||||
|
||||
self._dialog.show()
|
||||
|
||||
# Apply enabled/disabled state to installed plugins
|
||||
self.enabledChanged.emit()
|
||||
|
||||
@ -540,9 +540,7 @@ class Toolbox(QObject, Extension):
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def isEnabled(self, package_id: str) -> bool:
|
||||
if package_id in self._plugin_registry.getActivePlugins():
|
||||
return True
|
||||
return False
|
||||
return package_id in self._plugin_registry.getActivePlugins()
|
||||
|
||||
# Check for plugins that were installed with the old plugin browser
|
||||
def isOldPlugin(self, plugin_id: str) -> bool:
|
||||
@ -561,152 +559,169 @@ class Toolbox(QObject, Extension):
|
||||
# Make API Calls
|
||||
# --------------------------------------------------------------------------
|
||||
def _makeRequestByType(self, request_type: str) -> None:
|
||||
Logger.log("d", "Requesting %s metadata from server.", request_type)
|
||||
request = QNetworkRequest(self._request_urls[request_type])
|
||||
for header_name, header_value in self._request_headers:
|
||||
request.setRawHeader(header_name, header_value)
|
||||
if self._network_manager:
|
||||
self._network_manager.get(request)
|
||||
Logger.log("d", "Requesting [%s] metadata from server.", request_type)
|
||||
self._updateRequestHeader()
|
||||
url = self._request_urls[request_type]
|
||||
|
||||
callback = lambda r, rt = request_type: self._onDataRequestFinished(rt, r)
|
||||
error_callback = lambda r, e, rt = request_type: self._onDataRequestError(rt, r, e)
|
||||
self._application.getHttpRequestManager().get(url,
|
||||
headers_dict = self._request_headers,
|
||||
callback = callback,
|
||||
error_callback = error_callback)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def startDownload(self, url: str) -> None:
|
||||
Logger.log("i", "Attempting to download & install package from %s.", url)
|
||||
url = QUrl(url)
|
||||
self._download_request = QNetworkRequest(url)
|
||||
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
||||
# Patch for Qt 5.6-5.8
|
||||
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
|
||||
# Patch for Qt 5.9+
|
||||
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
|
||||
for header_name, header_value in self._request_headers:
|
||||
cast(QNetworkRequest, self._download_request).setRawHeader(header_name, header_value)
|
||||
self._download_reply = cast(QNetworkAccessManager, self._network_manager).get(self._download_request)
|
||||
|
||||
callback = lambda r: self._onDownloadFinished(r)
|
||||
error_callback = lambda r, e: self._onDownloadFailed(r, e)
|
||||
download_progress_callback = self._onDownloadProgress
|
||||
request_data = self._application.getHttpRequestManager().get(url, headers_dict = self._request_headers,
|
||||
callback = callback,
|
||||
error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback)
|
||||
|
||||
self._download_request_data = request_data
|
||||
self.setDownloadProgress(0)
|
||||
self.setIsDownloading(True)
|
||||
cast(QNetworkReply, self._download_reply).downloadProgress.connect(self._onDownloadProgress)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelDownload(self) -> None:
|
||||
Logger.log("i", "User cancelled the download of a package.")
|
||||
Logger.log("i", "User cancelled the download of a package. request %s", self._download_request_data)
|
||||
if self._download_request_data is not None:
|
||||
self._application.getHttpRequestManager().abortRequest(self._download_request_data)
|
||||
self._download_request_data = None
|
||||
self.resetDownload()
|
||||
|
||||
def resetDownload(self) -> None:
|
||||
if self._download_reply:
|
||||
try:
|
||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||
except (TypeError, RuntimeError): # Raised when the method is not connected to the signal yet.
|
||||
pass # Don't need to disconnect.
|
||||
try:
|
||||
self._download_reply.abort()
|
||||
except RuntimeError:
|
||||
# In some cases the garbage collector is a bit to agressive, which causes the dowload_reply
|
||||
# to be deleted (especially if the machine has been put to sleep). As we don't know what exactly causes
|
||||
# this (The issue probably lives in the bowels of (py)Qt somewhere), we can only catch and ignore it.
|
||||
pass
|
||||
self._download_reply = None
|
||||
self._download_request = None
|
||||
self.setDownloadProgress(0)
|
||||
self.setIsDownloading(False)
|
||||
|
||||
# Handlers for Network Events
|
||||
# --------------------------------------------------------------------------
|
||||
def _onNetworkAccessibleChanged(self, network_accessibility: QNetworkAccessManager.NetworkAccessibility) -> None:
|
||||
if network_accessibility == QNetworkAccessManager.NotAccessible:
|
||||
self.resetDownload()
|
||||
def _onDataRequestError(self, request_type: str, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||
Logger.log("e", "Request [%s] failed due to error [%s]: %s", request_type, error, reply.errorString())
|
||||
self.setViewPage("errored")
|
||||
|
||||
def _onRequestFinished(self, reply: QNetworkReply) -> None:
|
||||
if reply.error() == QNetworkReply.TimeoutError:
|
||||
Logger.log("w", "Got a timeout.")
|
||||
self.setViewPage("errored")
|
||||
self.resetDownload()
|
||||
def _onDataRequestFinished(self, request_type: str, reply: "QNetworkReply") -> None:
|
||||
if reply.operation() != QNetworkAccessManager.GetOperation:
|
||||
Logger.log("e", "_onDataRequestFinished() only handles GET requests but got [%s] instead", reply.operation())
|
||||
return
|
||||
|
||||
if reply.error() == QNetworkReply.HostNotFoundError:
|
||||
Logger.log("w", "Unable to reach server.")
|
||||
http_status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||
if http_status_code != 200:
|
||||
Logger.log("e", "Request type [%s] got non-200 HTTP response: [%s]", http_status_code)
|
||||
self.setViewPage("errored")
|
||||
self.resetDownload()
|
||||
return
|
||||
|
||||
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||
for response_type, url in self._request_urls.items():
|
||||
if reply.url() == url:
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
|
||||
try:
|
||||
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
data = bytes(reply.readAll())
|
||||
try:
|
||||
json_data = json.loads(data.decode("utf-8"))
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.log("e", "Failed to decode response data as JSON for request type [%s], response data [%s]",
|
||||
request_type, data)
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
# Check for errors:
|
||||
if "errors" in json_data:
|
||||
for error in json_data["errors"]:
|
||||
Logger.log("e", "%s", error["title"])
|
||||
return
|
||||
# Check for errors:
|
||||
if "errors" in json_data:
|
||||
for error in json_data["errors"]:
|
||||
Logger.log("e", "Request type [%s] got response showing error: %s", error["title"])
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
# Create model and apply metadata:
|
||||
if not self._models[response_type]:
|
||||
Logger.log("e", "Could not find the %s model.", response_type)
|
||||
break
|
||||
# Create model and apply metadata:
|
||||
if not self._models[request_type]:
|
||||
Logger.log("e", "Could not find the model for request type [%s].", request_type)
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
self._server_response_data[response_type] = json_data["data"]
|
||||
self._models[response_type].setMetadata(self._server_response_data[response_type])
|
||||
self._server_response_data[request_type] = json_data["data"]
|
||||
self._models[request_type].setMetadata(self._server_response_data[request_type])
|
||||
|
||||
if response_type == "packages":
|
||||
self._models[response_type].setFilter({"type": "plugin"})
|
||||
self.reBuildMaterialsModels()
|
||||
self.reBuildPluginsModels()
|
||||
self._notifyPackageManager()
|
||||
elif response_type == "authors":
|
||||
self._models[response_type].setFilter({"package_types": "material"})
|
||||
self._models[response_type].setFilter({"tags": "generic"})
|
||||
elif response_type == "updates":
|
||||
# Tell the package manager that there's a new set of updates available.
|
||||
packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]])
|
||||
self._package_manager.setPackagesWithUpdate(packages)
|
||||
if request_type == "packages":
|
||||
self._models[request_type].setFilter({"type": "plugin"})
|
||||
self.reBuildMaterialsModels()
|
||||
self.reBuildPluginsModels()
|
||||
self._notifyPackageManager()
|
||||
elif request_type == "authors":
|
||||
self._models[request_type].setFilter({"package_types": "material"})
|
||||
self._models[request_type].setFilter({"tags": "generic"})
|
||||
elif request_type == "updates":
|
||||
# Tell the package manager that there's a new set of updates available.
|
||||
packages = set([pkg["package_id"] for pkg in self._server_response_data[request_type]])
|
||||
self._package_manager.setPackagesWithUpdate(packages)
|
||||
elif request_type == "subscribed_packages":
|
||||
self._checkCompatibilities(json_data["data"])
|
||||
|
||||
self.metadataChanged.emit()
|
||||
self.metadataChanged.emit()
|
||||
|
||||
if self.isLoadingComplete():
|
||||
self.setViewPage("overview")
|
||||
if self.isLoadingComplete():
|
||||
self.setViewPage("overview")
|
||||
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.log("w", "Received invalid JSON for %s.", response_type)
|
||||
break
|
||||
else:
|
||||
Logger.log("w", "Unable to connect with the server, we got a response code %s while trying to connect to %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
|
||||
self.setViewPage("errored")
|
||||
self.resetDownload()
|
||||
elif reply.operation() == QNetworkAccessManager.PutOperation:
|
||||
# Ignore any operation that is not a get operation
|
||||
pass
|
||||
def _checkCompatibilities(self, json_data) -> None:
|
||||
user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
|
||||
user_installed_packages = self._package_manager.getUserInstalledPackages()
|
||||
|
||||
# We check if there are packages installed in Cloud Marketplace but not in Cura marketplace (discrepancy)
|
||||
package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
|
||||
if package_discrepancy:
|
||||
self._models["subscribed_packages"].addValue(package_discrepancy)
|
||||
self._models["subscribed_packages"].update()
|
||||
Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages")
|
||||
sync_message = Message(i18n_catalog.i18nc(
|
||||
"@info:generic",
|
||||
"\nDo you want to sync material and software packages with your account?"),
|
||||
lifetime=0,
|
||||
title=i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
|
||||
sync_message.addAction("sync",
|
||||
name=i18n_catalog.i18nc("@action:button", "Sync"),
|
||||
icon="",
|
||||
description="Sync your Cloud subscribed packages to your local environment.",
|
||||
button_align=Message.ActionButtonAlignment.ALIGN_RIGHT)
|
||||
|
||||
sync_message.actionTriggered.connect(self._onSyncButtonClicked)
|
||||
sync_message.show()
|
||||
|
||||
def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
|
||||
sync_message.hide()
|
||||
compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml"
|
||||
plugin_path_prefix = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||
if plugin_path_prefix:
|
||||
path = os.path.join(plugin_path_prefix, compatibility_dialog_path)
|
||||
self.compatibility_dialog_view = self._application.getInstance().createQmlComponent(path, {"toolbox": self})
|
||||
|
||||
# This function goes through all known remote versions of a package and notifies the package manager of this change
|
||||
def _notifyPackageManager(self):
|
||||
for package in self._server_response_data["packages"]:
|
||||
self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]))
|
||||
|
||||
def _onDownloadFinished(self, reply: "QNetworkReply") -> None:
|
||||
self.resetDownload()
|
||||
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s",
|
||||
json.loads(reply.readAll().data().decode("utf-8")))
|
||||
return
|
||||
# Must not delete the temporary file on Windows
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||
file_path = self._temp_plugin_file.name
|
||||
# Write first and close, otherwise on Windows, it cannot read the file
|
||||
self._temp_plugin_file.write(reply.readAll())
|
||||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
|
||||
def _onDownloadFailed(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", error)
|
||||
|
||||
self.resetDownload()
|
||||
|
||||
def _onDownloadProgress(self, bytes_sent: int, bytes_total: int) -> None:
|
||||
if bytes_total > 0:
|
||||
new_progress = bytes_sent / bytes_total * 100
|
||||
self.setDownloadProgress(new_progress)
|
||||
if bytes_sent == bytes_total:
|
||||
self.setIsDownloading(False)
|
||||
self._download_reply = cast(QNetworkReply, self._download_reply)
|
||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||
|
||||
# Check if the download was sucessfull
|
||||
if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
try:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.logException("w", "Failed to download package and failed to parse a response from it")
|
||||
finally:
|
||||
return
|
||||
# Must not delete the temporary file on Windows
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||
file_path = self._temp_plugin_file.name
|
||||
# Write first and close, otherwise on Windows, it cannot read the file
|
||||
self._temp_plugin_file.write(cast(QNetworkReply, self._download_reply).readAll())
|
||||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
Logger.log("d", "new download progress %s / %s : %s%%", bytes_sent, bytes_total, new_progress)
|
||||
|
||||
def _onDownloadComplete(self, file_path: str) -> None:
|
||||
Logger.log("i", "Download complete.")
|
||||
@ -721,6 +736,7 @@ class Toolbox(QObject, Extension):
|
||||
return
|
||||
|
||||
self.install(file_path)
|
||||
self.subscribe(package_info["package_id"])
|
||||
|
||||
# Getter & Setters for Properties:
|
||||
# --------------------------------------------------------------------------
|
||||
@ -772,39 +788,51 @@ class Toolbox(QObject, Extension):
|
||||
|
||||
# Exposed Models:
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def authorsModel(self) -> AuthorsModel:
|
||||
return cast(AuthorsModel, self._models["authors"])
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def subscribedPackagesModel(self) -> SubscribedPackagesModel:
|
||||
return cast(SubscribedPackagesModel, self._models["subscribed_packages"])
|
||||
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def has_compatible_packages(self) -> bool:
|
||||
return self._models["subscribed_packages"].hasCompatiblePackages()
|
||||
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def has_incompatible_packages(self) -> bool:
|
||||
return self._models["subscribed_packages"].hasIncompatiblePackages()
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def packagesModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["packages"])
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def pluginsShowcaseModel(self) -> PackagesModel:
|
||||
return self._plugins_showcase_model
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def pluginsAvailableModel(self) -> PackagesModel:
|
||||
return self._plugins_available_model
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def pluginsInstalledModel(self) -> PackagesModel:
|
||||
return self._plugins_installed_model
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsShowcaseModel(self) -> AuthorsModel:
|
||||
return self._materials_showcase_model
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsAvailableModel(self) -> AuthorsModel:
|
||||
return self._materials_available_model
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsInstalledModel(self) -> PackagesModel:
|
||||
return self._materials_installed_model
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def materialsGenericModel(self) -> PackagesModel:
|
||||
return self._materials_generic_model
|
||||
|
||||
|
@ -123,7 +123,9 @@ class ZeroConfClient:
|
||||
|
||||
# Request more data if info is not complete
|
||||
if not info.address:
|
||||
info = zero_conf.get_service_info(service_type, name)
|
||||
new_info = zero_conf.get_service_info(service_type, name)
|
||||
if new_info is not None:
|
||||
info = new_info
|
||||
|
||||
if info and info.address:
|
||||
type_of_device = info.properties.get(b"type", None)
|
||||
|
@ -88,7 +88,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
self._firmware_name_requested = False
|
||||
self._firmware_updater = AvrFirmwareUpdater(self)
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
|
||||
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath("USBPrinting"))
|
||||
self._monitor_view_qml_path = os.path.join(plugin_path, "MonitorItem.qml")
|
||||
|
||||
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
|
||||
|
||||
|
@ -239,7 +239,7 @@ class VersionUpgrade41to42(VersionUpgrade):
|
||||
#
|
||||
# This renames the renamed settings in the containers.
|
||||
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes=())
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
|
@ -104,7 +104,7 @@ class VersionUpgrade42to43(VersionUpgrade):
|
||||
#
|
||||
# This renames the renamed settings in the containers.
|
||||
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes=())
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
|
@ -52,7 +52,7 @@ class VersionUpgrade43to44(VersionUpgrade):
|
||||
#
|
||||
# This renames the renamed settings in the containers.
|
||||
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes=())
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
|
@ -3,10 +3,17 @@ from typing import Tuple, List
|
||||
import io
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
|
||||
# Merged preferences: machine_head_polygon and machine_head_with_fans_polygon -> machine_head_with_fans_polygon
|
||||
# When both are present, machine_head_polygon will be removed
|
||||
# When only one of the two is present, it's value will be used
|
||||
# Settings that were merged into one. Each one is a pair of settings. If both
|
||||
# are overwritten, the key wins. If only the key or the value is overwritten,
|
||||
# that value is used in the key.
|
||||
_merged_settings = {
|
||||
"machine_head_with_fans_polygon": "machine_head_polygon",
|
||||
"support_wall_count": "support_tree_wall_count"
|
||||
}
|
||||
|
||||
_removed_settings = {
|
||||
"support_tree_wall_thickness"
|
||||
}
|
||||
|
||||
class VersionUpgrade44to45(VersionUpgrade):
|
||||
def getCfgVersion(self, serialised: str) -> int:
|
||||
@ -35,20 +42,26 @@ class VersionUpgrade44to45(VersionUpgrade):
|
||||
#
|
||||
# This renames the renamed settings in the containers.
|
||||
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes=())
|
||||
parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["metadata"]["setting_version"] = "11"
|
||||
|
||||
if "values" in parser:
|
||||
# merge machine_head_with_fans_polygon (preferred) and machine_head_polygon
|
||||
if "machine_head_with_fans_polygon" in parser["values"]:
|
||||
if "machine_head_polygon" in parser["values"]:
|
||||
del parser["values"]["machine_head_polygon"]
|
||||
elif "machine_head_polygon" in parser["values"]:
|
||||
parser["values"]["machine_head_with_fans_polygon"] = parser["values"]["machine_head_polygon"]
|
||||
del parser["values"]["machine_head_polygon"]
|
||||
# Merged settings: When two settings are merged, one is preferred.
|
||||
# If the preferred one is available, that value is taken regardless
|
||||
# of the other one. If only the non-preferred one is available, that
|
||||
# value is moved to the preferred setting value.
|
||||
for preferred, removed in _merged_settings.items():
|
||||
if removed in parser["values"]:
|
||||
if preferred not in parser["values"]:
|
||||
parser["values"][preferred] = parser["values"][removed]
|
||||
del parser["values"][removed]
|
||||
|
||||
for removed in _removed_settings:
|
||||
if removed in parser["values"]:
|
||||
del parser["values"][removed]
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
|
15
plugins/XmlMaterialProfile/PluginInfo.py
Normal file
15
plugins/XmlMaterialProfile/PluginInfo.py
Normal file
@ -0,0 +1,15 @@
|
||||
from UM.PluginObject import PluginObject
|
||||
|
||||
|
||||
class PluginInfo(PluginObject):
|
||||
__instance = None # type: PluginInfo
|
||||
|
||||
def __init__(self, *args, **kwags):
|
||||
super().__init__(*args, **kwags)
|
||||
if PluginInfo.__instance is not None:
|
||||
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
||||
PluginInfo.__instance = self
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls, *args, **kwargs) -> "PluginInfo":
|
||||
return cls.__instance
|
@ -9,6 +9,7 @@ import sys
|
||||
from typing import Any, Dict, List, Optional, Tuple, cast, Set, Union
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from UM.Logger import Logger
|
||||
import UM.Dictionary
|
||||
@ -1068,7 +1069,8 @@ class XmlMaterialProfile(InstanceContainer):
|
||||
# This loads the mapping from a file.
|
||||
@classmethod
|
||||
def getProductIdMap(cls) -> Dict[str, List[str]]:
|
||||
product_to_id_file = os.path.join(os.path.dirname(sys.modules[cls.__module__].__file__), "product_to_id.json")
|
||||
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath("XmlMaterialProfile"))
|
||||
product_to_id_file = os.path.join(plugin_path, "product_to_id.json")
|
||||
with open(product_to_id_file, encoding = "utf-8") as f:
|
||||
product_to_id_map = json.load(f)
|
||||
product_to_id_map = {key: [value] for key, value in product_to_id_map.items()}
|
||||
@ -1106,6 +1108,12 @@ class XmlMaterialProfile(InstanceContainer):
|
||||
"break preparation speed": "material_break_preparation_speed",
|
||||
"break preparation temperature": "material_break_preparation_temperature",
|
||||
"break position": "material_break_retracted_position",
|
||||
"flush purge speed": "material_flush_purge_speed",
|
||||
"flush purge length": "material_flush_purge_length",
|
||||
"end of filament purge speed": "material_end_of_filament_purge_speed",
|
||||
"end of filament purge length": "material_end_of_filament_purge_length",
|
||||
"maximum park duration": "material_maximum_park_duration",
|
||||
"no load move factor": "material_no_load_move_factor",
|
||||
"break speed": "material_break_speed",
|
||||
"break temperature": "material_break_temperature"
|
||||
} # type: Dict[str, str]
|
||||
|
@ -407,6 +407,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SentryLogger": {
|
||||
"package_info": {
|
||||
"package_id": "SentryLogger",
|
||||
"package_type": "plugin",
|
||||
"display_name": "Sentry Logger",
|
||||
"description": "Logs certain events so that they can be used by the crash reporter",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SimulationView": {
|
||||
"package_info": {
|
||||
"package_id": "SimulationView",
|
||||
|
@ -58,9 +58,6 @@
|
||||
"machine_nozzle_head_distance": {
|
||||
"default_value": 5
|
||||
},
|
||||
"machine_nozzle_expansion_angle": {
|
||||
"default_value": 45
|
||||
},
|
||||
"machine_heat_zone_length": {
|
||||
"default_value": 20
|
||||
},
|
||||
|
152
resources/definitions/anet3d.def.json
Normal file
152
resources/definitions/anet3d.def.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "anet3d",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata": {
|
||||
"author": "Tiger.He",
|
||||
"manufacturer": "Anet",
|
||||
"category": "anet3d",
|
||||
"visible": false,
|
||||
"file_formats": "text/x-gcode",
|
||||
"first_start_actions": ["MachineSettingsAction"],
|
||||
|
||||
"preferred_variant_name": "0.4mm Nozzle",
|
||||
"preferred_quality_type": "standard",
|
||||
"preferred_material": "generic_pla",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"machine_max_feedrate_x": { "value": 500 },
|
||||
"machine_max_feedrate_y": { "value": 500 },
|
||||
"machine_max_feedrate_z": { "value": 10 },
|
||||
"machine_max_feedrate_e": { "value": 50 },
|
||||
|
||||
"machine_max_acceleration_x": { "value": 500 },
|
||||
"machine_max_acceleration_y": { "value": 500 },
|
||||
"machine_max_acceleration_z": { "value": 100 },
|
||||
"machine_max_acceleration_e": { "value": 5000 },
|
||||
"machine_acceleration": { "value": 500 },
|
||||
|
||||
"machine_max_jerk_xy": { "value": 10 },
|
||||
"machine_max_jerk_z": { "value": 0.4 },
|
||||
"machine_max_jerk_e": { "value": 5 },
|
||||
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
|
||||
"material_diameter": { "default_value": 1.75 },
|
||||
|
||||
"acceleration_print": { "value": 1000 },
|
||||
"acceleration_travel": { "value": 1000 },
|
||||
"acceleration_travel_layer_0": { "value": "acceleration_travel" },
|
||||
"acceleration_roofing": { "enabled": "acceleration_enabled and roofing_layer_count > 0 and top_layers > 0" },
|
||||
|
||||
"jerk_print": { "value": 30.0 },
|
||||
"jerk_travel": { "value": "jerk_print" },
|
||||
"jerk_travel_layer_0": { "value": "jerk_travel" },
|
||||
|
||||
"acceleration_enabled": { "value": true },
|
||||
"jerk_enabled": { "value": false },
|
||||
|
||||
"speed_print": { "value": 50.0 } ,
|
||||
"speed_infill": { "value": "speed_print * 2" },
|
||||
"speed_wall": { "value": "speed_print / 2" },
|
||||
"speed_wall_0": { "value": "speed_wall" },
|
||||
"speed_wall_x": { "value": "speed_wall" },
|
||||
"speed_topbottom": { "value": "speed_print / 2" },
|
||||
"speed_roofing": { "value": "speed_topbottom" },
|
||||
"speed_travel": { "value": "150.0 if speed_print < 60 else 250.0 if speed_print > 100 else speed_print * 2.5" },
|
||||
"speed_layer_0": { "value": "speed_print / 2" },
|
||||
"speed_print_layer_0": { "value": "speed_layer_0" },
|
||||
"speed_travel_layer_0": { "value": "100 if speed_layer_0 < 20 else 150 if speed_layer_0 > 30 else speed_layer_0 * 5" },
|
||||
"speed_prime_tower": { "value": "speed_print" },
|
||||
"speed_support": { "value": "speed_print" },
|
||||
"speed_support_interface": { "value": "speed_print" },
|
||||
"speed_z_hop": { "value": 5 },
|
||||
|
||||
"skirt_brim_speed": { "value": "speed_layer_0" },
|
||||
|
||||
"line_width": { "value": "machine_nozzle_size" },
|
||||
|
||||
"optimize_wall_printing_order": { "value": true },
|
||||
|
||||
"material_initial_print_temperature": { "value": "material_print_temperature" },
|
||||
"material_final_print_temperature": { "value": "material_print_temperature" },
|
||||
"material_flow": { "value": 100 },
|
||||
"travel_compensate_overlapping_walls_0_enabled": { "value": "False" },
|
||||
|
||||
"z_seam_type": { "value": "'back'" },
|
||||
"z_seam_corner": { "value": "'z_seam_corner_weighted'" },
|
||||
|
||||
"infill_sparse_density": { "value": "20" },
|
||||
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" },
|
||||
"infill_before_walls": { "value": true },
|
||||
"infill_overlap": { "value": 30.0 },
|
||||
"skin_overlap": { "value": 10.0 },
|
||||
"infill_wipe_dist": { "value": 1.0 },
|
||||
"wall_0_wipe_dist": { "value": 0.2 },
|
||||
|
||||
"fill_perimeter_gaps": { "value": "'everywhere'" },
|
||||
"fill_outline_gaps": { "value": false },
|
||||
"filter_out_tiny_gaps": { "value": true },
|
||||
|
||||
"retraction_speed": {
|
||||
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value": 200
|
||||
},
|
||||
"retraction_retract_speed": {
|
||||
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value": 200
|
||||
},
|
||||
"retraction_prime_speed": {
|
||||
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value": 200
|
||||
},
|
||||
|
||||
"retraction_hop_enabled": { "value": "False" },
|
||||
"retraction_hop": { "value": 1 },
|
||||
"retraction_combing": { "value": "'off' if retraction_hop_enabled else 'noskin'" },
|
||||
"retraction_combing_max_distance": { "value": 30 },
|
||||
"travel_avoid_other_parts": { "value": true },
|
||||
"travel_avoid_supports": { "value": true },
|
||||
"travel_retract_before_outer_wall": { "value": true },
|
||||
|
||||
"retraction_enable": { "value": true },
|
||||
"retraction_count_max": { "value": 100 },
|
||||
"retraction_extrusion_window": { "value": 10 },
|
||||
"retraction_min_travel": { "value": 1.5 },
|
||||
|
||||
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
|
||||
"cool_fan_enabled": { "value": true },
|
||||
"cool_min_layer_time": { "value": 10 },
|
||||
|
||||
"adaptive_layer_height_variation": { "value": 0.04 },
|
||||
"adaptive_layer_height_variation_step": { "value": 0.04 },
|
||||
|
||||
"meshfix_maximum_resolution": { "value": "0.05" },
|
||||
"meshfix_maximum_travel_resolution": { "value": "meshfix_maximum_resolution" },
|
||||
|
||||
"top_bottom_thickness": {"value": "layer_height_0 + layer_height * 3" },
|
||||
"wall_thickness": {"value": "line_width * 2" },
|
||||
|
||||
"material_print_temperature": {"minimum_value": "0"},
|
||||
"material_bed_temperature": {"minimum_value": "0"},
|
||||
"material_standby_temperature": {"minimum_value": "0"},
|
||||
|
||||
"extruder_prime_pos_y":{"minimum_value": "0","maximum_value": "machine_depth"},
|
||||
"extruder_prime_pos_x":{"minimum_value": "0","maximum_value": "machine_width"},
|
||||
"relative_extrusion":{"value": false,"enabled": false},
|
||||
|
||||
"machine_use_extruder_offset_to_offset_coords": {"default_value": true},
|
||||
"machine_gcode_flavor": {"default_value": "RepRap (Marlin/Sprinter)"},
|
||||
|
||||
"machine_center_is_zero": {
|
||||
"default_value": false
|
||||
},
|
||||
"gantry_height": {
|
||||
"value": "0"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_a2 plus.def.json
Normal file
31
resources/definitions/anet3d_a2 plus.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet A2 PLUS",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet A2 PLUS" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 270
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_a2.def.json
Normal file
31
resources/definitions/anet3d_a2.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet A2",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet A2" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_a6.def.json
Normal file
31
resources/definitions/anet3d_a6.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet A6",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet A6" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 250
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_a8 plus.def.json
Normal file
31
resources/definitions/anet3d_a8 plus.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet A8 PLUS",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet A8 PLUS" },
|
||||
"machine_width": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 350
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_a8.def.json
Normal file
31
resources/definitions/anet3d_a8.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet A8",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet A8" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 240
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_e10.def.json
Normal file
31
resources/definitions/anet3d_e10.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet E10",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet E10" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 270
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_e12.def.json
Normal file
31
resources/definitions/anet3d_e12.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet E12",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet E12" },
|
||||
"machine_width": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 400
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_e16.def.json
Normal file
31
resources/definitions/anet3d_e16.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet E16",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet E16" },
|
||||
"machine_width": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 400
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_et4 pro.def.json
Normal file
31
resources/definitions/anet3d_et4 pro.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet ET4 PRO",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet ET4 PRO" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 250
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_et4 x.def.json
Normal file
31
resources/definitions/anet3d_et4 x.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet ET4 X",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet ET4 X" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 250
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_et4.def.json
Normal file
31
resources/definitions/anet3d_et4.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet ET4",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet ET4" },
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 250
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_et5 x.def.json
Normal file
31
resources/definitions/anet3d_et5 x.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet ET5 X",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet ET5 X" },
|
||||
"machine_width": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 400
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
31
resources/definitions/anet3d_et5.def.json
Normal file
31
resources/definitions/anet3d_et5.def.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet ET5",
|
||||
"inherits": "anet3d",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet3d_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet ET5" },
|
||||
"machine_width": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 400
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ;Home\nG1 Z15.0 F2000 ;Move the platform"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nM140 S0\nG92 E80\nG1 E-80 F2000\nG28 X0 Y0\nM84"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Anet A6",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"author": "Mark",
|
||||
"manufacturer": "Anet",
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "aneta6_platform.stl",
|
||||
"platform_offset": [0, -3.4, 0],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "anet_a6_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Anet A6" },
|
||||
"machine_heated_bed": {
|
||||
"default_value": true
|
||||
},
|
||||
"machine_width": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 250
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 220
|
||||
},
|
||||
"machine_center_is_zero": {
|
||||
"default_value": false
|
||||
},
|
||||
"gantry_height": {
|
||||
"value": "55"
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G21 ;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\nM84 ;steppers off\nM0 S12 ;wait 12 seconds\nM17 ;turn steppers on\nG1 Z10.0 F300 ;move the platform down 10mm\nG92 E0 ;zero the extruded length\nG1 F200 E8 ;extrude 8mm of feed stock\nG92 E0 ;zero the extruded length again\nM0 S5 ;wait 5 seconds\nG1 F9000\nM117 Printing..."
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+4 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG1 Y210 F9000 ;move out to get part off\nM84 ;steppers off\nG90 ;absolute positioning"
|
||||
}
|
||||
}
|
||||
}
|
58
resources/definitions/beamup_s.def.json
Normal file
58
resources/definitions/beamup_s.def.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "BeamUp S",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"author": "BeamUp",
|
||||
"manufacturer": "BeamUp",
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "beamup_s.stl",
|
||||
"platform_offset": [0, -5, -10],
|
||||
"has_machine_quality": true,
|
||||
"has_materials": true,
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "beamup_s_extruder_0"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": {
|
||||
"default_value": "BeamUp S"
|
||||
},
|
||||
"machine_width": {
|
||||
"default_value": 200
|
||||
},
|
||||
"machine_depth": {
|
||||
"default_value": 180
|
||||
},
|
||||
"machine_height": {
|
||||
"default_value": 130
|
||||
},
|
||||
"machine_heated_bed": {
|
||||
"default_value": false
|
||||
},
|
||||
"machine_center_is_zero": {
|
||||
"default_value": false
|
||||
},
|
||||
"machine_nozzle_heat_up_speed": {
|
||||
"default_value": 2
|
||||
},
|
||||
"machine_nozzle_cool_down_speed": {
|
||||
"default_value": 2
|
||||
},
|
||||
"gantry_height": {
|
||||
"value": "0"
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G28 ; home\nG29 ; level\nM80 ; led\nG1 Z15.0 F6000\nT0\nG92 E0.0000\nG1 E-1.4500 F1800\nG1 X5 Y0 Z0.300 F6000\nM300 S3000 P300\nG1 E1.0000 F1800\nG92 E0.0000\nG1 X180 Y0 E15 F662"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "G28 ; home\nM104 S0 ; turn off\n M140 S0 ; turn off\nM84 ; disable motors\nM107 ; fan off"
|
||||
}
|
||||
}
|
||||
}
|
@ -258,7 +258,7 @@
|
||||
"support_interface_density": { "value": 33.333 },
|
||||
"support_interface_pattern": { "value": "'grid'" },
|
||||
"support_interface_skip_height": { "value": 0.2 },
|
||||
"minimum_support_area": { "value": 5 },
|
||||
"minimum_support_area": { "value": 2 },
|
||||
"minimum_interface_area": { "value": 10 },
|
||||
"top_bottom_thickness": {"value": "layer_height_0 + layer_height * 3" },
|
||||
"wall_thickness": {"value": "line_width * 2" }
|
||||
|
0
resources/definitions/deltacomb.def.json
Executable file → Normal file
0
resources/definitions/deltacomb.def.json
Executable file → Normal file
@ -2084,7 +2084,6 @@
|
||||
"maximum_value": "machine_height",
|
||||
"type": "float",
|
||||
"value": "0",
|
||||
"comment": "This was put at 0 to keep the default behaviour the same, but in the original PR the 'value' was: resolveOrValue('infill_sparse_thickness') * (4 if infill_sparse_density < 12.5 else (3 if infill_sparse_density < 25 else (2 if infill_sparse_density < 50 else 1)))",
|
||||
"limit_to_extruder": "infill_extruder_nr",
|
||||
"enabled": "infill_sparse_density > 0",
|
||||
"settable_per_mesh": true,
|
||||
@ -2420,6 +2419,54 @@
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"material_flush_purge_speed":
|
||||
{
|
||||
"label": "Flush Purge Speed",
|
||||
"description": "Material Station internal value",
|
||||
"type": "float",
|
||||
"default_value": 0.5,
|
||||
"enabled": false
|
||||
},
|
||||
"material_flush_purge_length":
|
||||
{
|
||||
"label": "Flush Purge Length",
|
||||
"description": "Material Station internal value",
|
||||
"type": "float",
|
||||
"default_value": 60,
|
||||
"enabled": false
|
||||
},
|
||||
"material_end_of_filament_purge_speed":
|
||||
{
|
||||
"label": "End Of Filament Purge Speed",
|
||||
"description": "Material Station internal value",
|
||||
"type": "float",
|
||||
"default_value": 0.5,
|
||||
"enabled": false
|
||||
},
|
||||
"material_end_of_filament_purge_length":
|
||||
{
|
||||
"label": "End Of Filament Purge Length",
|
||||
"description": "Material Station internal value",
|
||||
"type": "float",
|
||||
"default_value": 20,
|
||||
"enabled": false
|
||||
},
|
||||
"material_maximum_park_duration":
|
||||
{
|
||||
"label": "Maximum Park Duration",
|
||||
"description": "Material Station internal value",
|
||||
"type": "float",
|
||||
"default_value": 300,
|
||||
"enabled": false
|
||||
},
|
||||
"material_no_load_move_factor":
|
||||
{
|
||||
"label": "No Load Move Factor",
|
||||
"description": "Material Station internal value",
|
||||
"type": "float",
|
||||
"default_value": 0.940860215,
|
||||
"enabled": false
|
||||
},
|
||||
"material_flow":
|
||||
{
|
||||
"label": "Flow",
|
||||
@ -2634,150 +2681,6 @@
|
||||
"maximum_value_warning": "150",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"retraction_enable":
|
||||
{
|
||||
"label": "Enable Retraction",
|
||||
"description": "Retract the filament when the nozzle is moving over a non-printed area. ",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retract_at_layer_change":
|
||||
{
|
||||
"label": "Retract at Layer Change",
|
||||
"description": "Retract the filament when the nozzle is moving to the next layer.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_amount":
|
||||
{
|
||||
"label": "Retraction Distance",
|
||||
"description": "The length of material retracted during a retraction move.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 6.5,
|
||||
"minimum_value_warning": "-0.0001",
|
||||
"maximum_value_warning": "10.0",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_speed":
|
||||
{
|
||||
"label": "Retraction Speed",
|
||||
"description": "The speed at which the filament is retracted and primed during a retraction move.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 25,
|
||||
"minimum_value": "0.0001",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
"retraction_retract_speed":
|
||||
{
|
||||
"label": "Retraction Retract Speed",
|
||||
"description": "The speed at which the filament is retracted during a retraction move.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 25,
|
||||
"minimum_value": "0.0001",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value_warning": "70",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"value": "retraction_speed",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_prime_speed":
|
||||
{
|
||||
"label": "Retraction Prime Speed",
|
||||
"description": "The speed at which the filament is primed during a retraction move.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 25,
|
||||
"minimum_value": "0.0001",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value_warning": "70",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"value": "retraction_speed",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"retraction_extra_prime_amount":
|
||||
{
|
||||
"label": "Retraction Extra Prime Amount",
|
||||
"description": "Some material can ooze away during a travel move, which can be compensated for here.",
|
||||
"unit": "mm³",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value_warning": "-0.0001",
|
||||
"maximum_value_warning": "5.0",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_min_travel":
|
||||
{
|
||||
"label": "Retraction Minimum Travel",
|
||||
"description": "The minimum distance of travel needed for a retraction to happen at all. This helps to get fewer retractions in a small area.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 1.5,
|
||||
"value": "line_width * 2",
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "line_width * 1.5",
|
||||
"maximum_value_warning": "10",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_count_max":
|
||||
{
|
||||
"label": "Maximum Retraction Count",
|
||||
"description": "This setting limits the number of retractions occurring within the minimum extrusion distance window. Further retractions within this window will be ignored. This avoids retracting repeatedly on the same piece of filament, as that can flatten the filament and cause grinding issues.",
|
||||
"default_value": 90,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"type": "int",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_extrusion_window":
|
||||
{
|
||||
"label": "Minimum Extrusion Distance Window",
|
||||
"description": "The window in which the maximum retraction count is enforced. This value should be approximately the same as the retraction distance, so that effectively the number of times a retraction passes the same patch of material is limited.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 4.5,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "retraction_amount * 2",
|
||||
"value": "retraction_amount",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"limit_support_retractions":
|
||||
{
|
||||
"label": "Limit Support Retractions",
|
||||
"description": "Omit retraction when moving from support to support in a straight line. Enabling this setting saves print time, but can lead to excessive stringing within the support structure.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": "retraction_enable and (support_enable or support_tree_enable)",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"material_standby_temperature":
|
||||
{
|
||||
"label": "Standby Temperature",
|
||||
@ -2791,83 +2694,6 @@
|
||||
"enabled": "extruders_enabled_count > 1 and machine_nozzle_temp_enabled",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"switch_extruder_retraction_amount":
|
||||
{
|
||||
"label": "Nozzle Switch Retraction Distance",
|
||||
"description": "The amount of retraction when switching extruders. Set to 0 for no retraction at all. This should generally be the same as the length of the heat zone.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"enabled": "retraction_enable",
|
||||
"default_value": 20,
|
||||
"value": "machine_heat_zone_length",
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"switch_extruder_retraction_speeds":
|
||||
{
|
||||
"label": "Nozzle Switch Retraction Speed",
|
||||
"description": "The speed at which the filament is retracted. A higher retraction speed works better, but a very high retraction speed can lead to filament grinding.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"enabled": "retraction_enable",
|
||||
"default_value": 20,
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
"switch_extruder_retraction_speed":
|
||||
{
|
||||
"label": "Nozzle Switch Retract Speed",
|
||||
"description": "The speed at which the filament is retracted during a nozzle switch retract.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"enabled": "retraction_enable",
|
||||
"default_value": 20,
|
||||
"value": "switch_extruder_retraction_speeds",
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"switch_extruder_prime_speed":
|
||||
{
|
||||
"label": "Nozzle Switch Prime Speed",
|
||||
"description": "The speed at which the filament is pushed back after a nozzle switch retraction.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"enabled": "retraction_enable",
|
||||
"default_value": 20,
|
||||
"value": "switch_extruder_retraction_speeds",
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch_extruder_extra_prime_amount":
|
||||
{
|
||||
"label": "Nozzle Switch Extra Prime Amount",
|
||||
"description": "Extra material to prime after nozzle switching.",
|
||||
"type": "float",
|
||||
"unit": "mm³",
|
||||
"default_value": 0,
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3783,6 +3609,150 @@
|
||||
"type": "category",
|
||||
"children":
|
||||
{
|
||||
"retraction_enable":
|
||||
{
|
||||
"label": "Enable Retraction",
|
||||
"description": "Retract the filament when the nozzle is moving over a non-printed area. ",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retract_at_layer_change":
|
||||
{
|
||||
"label": "Retract at Layer Change",
|
||||
"description": "Retract the filament when the nozzle is moving to the next layer.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_amount":
|
||||
{
|
||||
"label": "Retraction Distance",
|
||||
"description": "The length of material retracted during a retraction move.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 6.5,
|
||||
"minimum_value_warning": "-0.0001",
|
||||
"maximum_value_warning": "10.0",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_speed":
|
||||
{
|
||||
"label": "Retraction Speed",
|
||||
"description": "The speed at which the filament is retracted and primed during a retraction move.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 25,
|
||||
"minimum_value": "0.0001",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
"retraction_retract_speed":
|
||||
{
|
||||
"label": "Retraction Retract Speed",
|
||||
"description": "The speed at which the filament is retracted during a retraction move.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 25,
|
||||
"minimum_value": "0.0001",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value_warning": "70",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"value": "retraction_speed",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_prime_speed":
|
||||
{
|
||||
"label": "Retraction Prime Speed",
|
||||
"description": "The speed at which the filament is primed during a retraction move.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 25,
|
||||
"minimum_value": "0.0001",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value_warning": "70",
|
||||
"enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"",
|
||||
"value": "retraction_speed",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"retraction_extra_prime_amount":
|
||||
{
|
||||
"label": "Retraction Extra Prime Amount",
|
||||
"description": "Some material can ooze away during a travel move, which can be compensated for here.",
|
||||
"unit": "mm³",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value_warning": "-0.0001",
|
||||
"maximum_value_warning": "5.0",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_min_travel":
|
||||
{
|
||||
"label": "Retraction Minimum Travel",
|
||||
"description": "The minimum distance of travel needed for a retraction to happen at all. This helps to get fewer retractions in a small area.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 1.5,
|
||||
"value": "line_width * 2",
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "line_width * 1.5",
|
||||
"maximum_value_warning": "10",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_count_max":
|
||||
{
|
||||
"label": "Maximum Retraction Count",
|
||||
"description": "This setting limits the number of retractions occurring within the minimum extrusion distance window. Further retractions within this window will be ignored. This avoids retracting repeatedly on the same piece of filament, as that can flatten the filament and cause grinding issues.",
|
||||
"default_value": 90,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"type": "int",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_extrusion_window":
|
||||
{
|
||||
"label": "Minimum Extrusion Distance Window",
|
||||
"description": "The window in which the maximum retraction count is enforced. This value should be approximately the same as the retraction distance, so that effectively the number of times a retraction passes the same patch of material is limited.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 4.5,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "retraction_amount * 2",
|
||||
"value": "retraction_amount",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"limit_support_retractions":
|
||||
{
|
||||
"label": "Limit Support Retractions",
|
||||
"description": "Omit retraction when moving from support to support in a straight line. Enabling this setting saves print time, but can lead to excessive stringing within the support structure.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": "retraction_enable and (support_enable or support_tree_enable)",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"retraction_combing":
|
||||
{
|
||||
"label": "Combing Mode",
|
||||
@ -4233,8 +4203,8 @@
|
||||
"minimum_value_warning": "1 if support_pattern == 'concentric' else 0",
|
||||
"maximum_value_warning": "3",
|
||||
"type": "int",
|
||||
"value": "1 if (support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'concentric') else 0",
|
||||
"enabled": "support_enable",
|
||||
"value": "1 if support_tree_enable else (1 if (support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'concentric') else 0)",
|
||||
"enabled": "support_enable or support_tree_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
@ -4551,6 +4521,7 @@
|
||||
"type": "float",
|
||||
"default_value": 0.0,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "5",
|
||||
"enabled": "support_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
@ -5154,6 +5125,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"brim_gap":
|
||||
{
|
||||
"label": "Brim Distance",
|
||||
"description": "The horizontal distance between the first brim line and the outline of the first layer of the print. A small gap can make the brim easier to remove while still providing the thermal benefits.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "skirt_brim_line_width",
|
||||
"enabled": "resolveOrValue('adhesion_type') == 'brim'",
|
||||
"settable_per_mesh": true,
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"brim_replaces_support":
|
||||
{
|
||||
"label": "Brim Replaces Support",
|
||||
@ -5782,6 +5767,83 @@
|
||||
"maximum_value_warning": "20",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
"switch_extruder_retraction_amount":
|
||||
{
|
||||
"label": "Nozzle Switch Retraction Distance",
|
||||
"description": "The amount of retraction when switching extruders. Set to 0 for no retraction at all. This should generally be the same as the length of the heat zone.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"enabled": "retraction_enable and extruders_enabled_count > 1",
|
||||
"default_value": 20,
|
||||
"value": "machine_heat_zone_length",
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"switch_extruder_retraction_speeds":
|
||||
{
|
||||
"label": "Nozzle Switch Retraction Speed",
|
||||
"description": "The speed at which the filament is retracted. A higher retraction speed works better, but a very high retraction speed can lead to filament grinding.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"enabled": "retraction_enable and extruders_enabled_count > 1",
|
||||
"default_value": 20,
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
"switch_extruder_retraction_speed":
|
||||
{
|
||||
"label": "Nozzle Switch Retract Speed",
|
||||
"description": "The speed at which the filament is retracted during a nozzle switch retract.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"enabled": "retraction_enable and extruders_enabled_count > 1",
|
||||
"default_value": 20,
|
||||
"value": "switch_extruder_retraction_speeds",
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"switch_extruder_prime_speed":
|
||||
{
|
||||
"label": "Nozzle Switch Prime Speed",
|
||||
"description": "The speed at which the filament is pushed back after a nozzle switch retraction.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"enabled": "retraction_enable and extruders_enabled_count > 1",
|
||||
"default_value": 20,
|
||||
"value": "switch_extruder_retraction_speeds",
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "1",
|
||||
"maximum_value": "machine_max_feedrate_e if retraction_enable else float('inf')",
|
||||
"maximum_value_warning": "70",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch_extruder_extra_prime_amount":
|
||||
{
|
||||
"label": "Nozzle Switch Extra Prime Amount",
|
||||
"description": "Extra material to prime after nozzle switching.",
|
||||
"type": "float",
|
||||
"unit": "mm³",
|
||||
"default_value": 0,
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"enabled": "retraction_enable and extruders_enabled_count > 1",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -5799,6 +5861,7 @@
|
||||
"description": "Ignore the internal geometry arising from overlapping volumes within a mesh and print the volumes as one. This may cause unintended internal cavities to disappear.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"value": "magic_mesh_surface_mode != 'surface'",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"meshfix_union_all_remove_holes":
|
||||
@ -6172,38 +6235,6 @@
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_tree_wall_thickness":
|
||||
{
|
||||
"label": "Tree Support Wall Thickness",
|
||||
"description": "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "wall_line_width",
|
||||
"default_value": 0.8,
|
||||
"value": "support_line_width",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"enabled": "support_tree_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
"support_tree_wall_count":
|
||||
{
|
||||
"label": "Tree Support Wall Line Count",
|
||||
"description": "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily.",
|
||||
"type": "int",
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "1",
|
||||
"default_value": 1,
|
||||
"value": "round(support_tree_wall_thickness / support_line_width)",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"enabled": "support_tree_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"slicing_tolerance":
|
||||
{
|
||||
"label": "Slicing Tolerance",
|
||||
@ -7386,7 +7417,7 @@
|
||||
"clean_between_layers":
|
||||
{
|
||||
"label": "Wipe Nozzle Between Layers",
|
||||
"description": "Whether to include nozzle wipe G-Code between layers. Enabling this setting could influence behavior of retract at layer change. Please use Wipe Retraction settings to control retraction at layers where the wipe script will be working.",
|
||||
"description": "Whether to include nozzle wipe G-Code between layers (maximum 1 per layer). Enabling this setting could influence behavior of retract at layer change. Please use Wipe Retraction settings to control retraction at layers where the wipe script will be working.",
|
||||
"default_value": false,
|
||||
"type": "bool",
|
||||
"settable_per_mesh": false,
|
||||
@ -7396,7 +7427,7 @@
|
||||
"max_extrusion_before_wipe":
|
||||
{
|
||||
"label": "Material Volume Between Wipes",
|
||||
"description": "Maximum material, that can be extruded before another nozzle wipe is initiated.",
|
||||
"description": "Maximum material that can be extruded before another nozzle wipe is initiated. If this value is less than the volume of material required in a layer, the setting has no effect in this layer, i.e. it is limited to one wipe per layer.",
|
||||
"default_value": 10,
|
||||
"type": "float",
|
||||
"unit": "mm³",
|
||||
@ -7620,7 +7651,7 @@
|
||||
"default_value": 50,
|
||||
"minimum_value": "1",
|
||||
"minimum_value_warning": "25",
|
||||
"maximum_value": "100",
|
||||
"maximum_value_warning": "100",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"small_feature_speed_factor_0":
|
||||
@ -7633,7 +7664,7 @@
|
||||
"value": "small_feature_speed_factor",
|
||||
"minimum_value": "1",
|
||||
"minimum_value_warning": "25",
|
||||
"maximum_value": "100",
|
||||
"maximum_value_warning": "100",
|
||||
"settable_per_mesh": true
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user