mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-07-03 07:25:10 +08:00
commit
8313246c1c
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,9 +30,6 @@ cura.desktop
|
||||
.pydevproject
|
||||
.settings
|
||||
|
||||
# Debian packaging
|
||||
debian*
|
||||
|
||||
#Externally located plug-ins.
|
||||
plugins/Doodle3D-cura-plugin
|
||||
plugins/GodMode
|
||||
|
9
Jenkinsfile
vendored
9
Jenkinsfile
vendored
@ -14,9 +14,14 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
|
||||
dir('build') {
|
||||
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
||||
stage('Build') {
|
||||
def branch = env.BRANCH_NAME
|
||||
if(!(branch =~ /^2.\d+$/)) {
|
||||
branch = "master"
|
||||
}
|
||||
|
||||
// Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
|
||||
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/master")
|
||||
cmake("..", "-DCMAKE_PREFIX_PATH=${env.CURA_ENVIRONMENT_PATH} -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=${uranium_dir}")
|
||||
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
|
||||
cmake("..", "-DCMAKE_PREFIX_PATH=${env.CURA_ENVIRONMENT_PATH}/${branch} -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=${uranium_dir}")
|
||||
}
|
||||
|
||||
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
|
||||
|
10
README.md
10
README.md
@ -14,10 +14,11 @@ For crashes and similar issues, please attach the following information:
|
||||
|
||||
* (On Windows) The log as produced by dxdiag (start -> run -> dxdiag -> save output)
|
||||
* The Cura GUI log file, located at
|
||||
* $User/AppData/Local/cura/cura.log (Windows)
|
||||
* $User/Library/Application Support/cura (OSX)
|
||||
* $USER/.local/share/cura (Ubuntu/Linux)
|
||||
* The Cura Engine log, using Help -> Show Engine Log
|
||||
* %APPDATA%\cura\\`<Cura version>`\cura.log (Windows), or usually C:\Users\\`<your username>`\AppData\Roaming\cura\\`<Cura version>`\cura.log
|
||||
* $User/Library/Application Support/cura/`<Cura version>`/cura.log (OSX)
|
||||
* $USER/.local/share/cura/`<Cura version>`/cura.log (Ubuntu/Linux)
|
||||
|
||||
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
@ -51,6 +52,7 @@ Third party plugins
|
||||
* [Auto orientation](https://github.com/nallath/CuraOrientationPlugin): Calculate the optimal orientation for a model.
|
||||
* [OctoPrint Plugin](https://github.com/fieldofview/OctoPrintPlugin): Send printjobs directly to OctoPrint and monitor their progress in Cura.
|
||||
* [WirelessPrinting Plugin](https://github.com/probonopd/WirelessPrinting): Print wirelessly from Cura to your 3D printer connected to an ESP8266 module.
|
||||
* [Electric Print Cost Calculator Plugin](https://github.com/zoff99/ElectricPrintCostCalculator): Calculate the electric costs of a print.
|
||||
|
||||
Making profiles for other printers
|
||||
----------------------------------
|
||||
|
@ -104,7 +104,6 @@ class BuildVolume(SceneNode):
|
||||
# but it does not update the disallowed areas after material change
|
||||
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
|
||||
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
if self._global_container_stack:
|
||||
self._change_timer.start()
|
||||
@ -637,7 +636,7 @@ class BuildVolume(SceneNode):
|
||||
result[extruder.getId()] = []
|
||||
|
||||
#Currently, the only normally printed object is the prime tower.
|
||||
if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable") == True:
|
||||
if ExtruderManager.getInstance().getResolveOrValue("prime_tower_enable"):
|
||||
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
|
||||
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
||||
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||
@ -678,7 +677,7 @@ class BuildVolume(SceneNode):
|
||||
for extruder in used_extruders:
|
||||
prime_blob_enabled = extruder.getProperty("prime_blob_enable", "value")
|
||||
prime_x = extruder.getProperty("extruder_prime_pos_x", "value")
|
||||
prime_y = - extruder.getProperty("extruder_prime_pos_y", "value")
|
||||
prime_y = -extruder.getProperty("extruder_prime_pos_y", "value")
|
||||
|
||||
#Ignore extruder prime position if it is not set or if blob is disabled
|
||||
if (prime_x == 0 and prime_y == 0) or not prime_blob_enabled:
|
||||
@ -717,13 +716,18 @@ class BuildVolume(SceneNode):
|
||||
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
|
||||
machine_disallowed_polygons.append(polygon)
|
||||
|
||||
# For certain machines we don't need to compute disallowed areas for each nozzle.
|
||||
# So we check here and only do the nozzle offsetting if needed.
|
||||
no_nozzle_offsetting_for_disallowed_areas = self._global_container_stack.getMetaDataEntry(
|
||||
"no_nozzle_offsetting_for_disallowed_areas", False)
|
||||
|
||||
result = {}
|
||||
for extruder in used_extruders:
|
||||
extruder_id = extruder.getId()
|
||||
offset_x = extruder.getProperty("machine_nozzle_offset_x", "value")
|
||||
if offset_x is None:
|
||||
offset_x = 0
|
||||
offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
|
||||
offset_y = -extruder.getProperty("machine_nozzle_offset_y", "value")
|
||||
if offset_y is None:
|
||||
offset_y = 0
|
||||
result[extruder_id] = []
|
||||
@ -736,14 +740,17 @@ class BuildVolume(SceneNode):
|
||||
right_unreachable_border = 0
|
||||
top_unreachable_border = 0
|
||||
bottom_unreachable_border = 0
|
||||
#The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders.
|
||||
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
|
||||
other_offset_y = other_extruder.getProperty("machine_nozzle_offset_y", "value")
|
||||
left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x)
|
||||
right_unreachable_border = max(right_unreachable_border, other_offset_x - offset_x)
|
||||
top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y)
|
||||
bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y)
|
||||
|
||||
# Only do nozzle offsetting if needed
|
||||
if not no_nozzle_offsetting_for_disallowed_areas:
|
||||
#The build volume is defined as the union of the area that all extruders can reach, so we need to know the relative offset to all extruders.
|
||||
for other_extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
other_offset_x = other_extruder.getProperty("machine_nozzle_offset_x", "value")
|
||||
other_offset_y = -other_extruder.getProperty("machine_nozzle_offset_y", "value")
|
||||
left_unreachable_border = min(left_unreachable_border, other_offset_x - offset_x)
|
||||
right_unreachable_border = max(right_unreachable_border, other_offset_x - offset_x)
|
||||
top_unreachable_border = min(top_unreachable_border, other_offset_y - offset_y)
|
||||
bottom_unreachable_border = max(bottom_unreachable_border, other_offset_y - offset_y)
|
||||
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
||||
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
||||
|
||||
@ -871,7 +878,7 @@ class BuildVolume(SceneNode):
|
||||
else:
|
||||
extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value")
|
||||
|
||||
if extruder_index == "-1": # If extruder index is -1 use global instead
|
||||
if str(extruder_index) == "-1": # If extruder index is -1 use global instead
|
||||
stack = self._global_container_stack
|
||||
else:
|
||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
|
||||
|
@ -298,7 +298,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
self._onChanged()
|
||||
|
||||
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
||||
def _getSettingProperty(self, setting_key, property="value"):
|
||||
def _getSettingProperty(self, setting_key, property = "value"):
|
||||
per_mesh_stack = self._node.callDecoration("getStack")
|
||||
if per_mesh_stack:
|
||||
return per_mesh_stack.getProperty(setting_key, property)
|
||||
@ -314,10 +314,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"]
|
||||
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
return extruder_stack.getProperty(setting_key, property)
|
||||
else: #Limit_to_extruder is set. Use that one.
|
||||
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
|
||||
stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
return stack.getProperty(setting_key, property)
|
||||
else: #Limit_to_extruder is set. The global stack handles this then.
|
||||
return self._global_stack.getProperty(setting_key, property)
|
||||
|
||||
## Returns true if node is a descendant or the same as the root node.
|
||||
def __isDescendant(self, root, node):
|
||||
|
@ -2,6 +2,9 @@ import sys
|
||||
import platform
|
||||
import traceback
|
||||
import webbrowser
|
||||
import faulthandler
|
||||
import tempfile
|
||||
import os
|
||||
import urllib
|
||||
|
||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QCoreApplication
|
||||
@ -91,6 +94,17 @@ def show(exception_type, value, tb):
|
||||
crash_info = "Version: {0}\nPlatform: {1}\nQt: {2}\nPyQt: {3}\n\nException:\n{4}"
|
||||
crash_info = crash_info.format(version, platform.platform(), QT_VERSION_STR, PYQT_VERSION_STR, trace)
|
||||
|
||||
tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True)
|
||||
os.close(tmp_file_fd)
|
||||
with open(tmp_file_path, "w") as f:
|
||||
faulthandler.dump_traceback(f, all_threads=True)
|
||||
with open(tmp_file_path, "r") as f:
|
||||
data = f.read()
|
||||
|
||||
msg = "-------------------------\n"
|
||||
msg += data
|
||||
crash_info += "\n\n" + msg
|
||||
|
||||
textarea.setText(crash_info)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Close, dialog)
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtNetwork import QLocalServer
|
||||
from PyQt5.QtNetwork import QLocalSocket
|
||||
|
||||
@ -47,6 +48,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from cura.Settings.MachineNameValidator import MachineNameValidator
|
||||
from cura.Settings.ProfilesModel import ProfilesModel
|
||||
from cura.Settings.MaterialsModel import MaterialsModel
|
||||
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
|
||||
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
||||
from cura.Settings.UserProfilesModel import UserProfilesModel
|
||||
@ -62,6 +64,7 @@ from . import CameraImageProvider
|
||||
from . import MachineActionManager
|
||||
|
||||
from cura.Settings.MachineManager import MachineManager
|
||||
from cura.Settings.MaterialManager import MaterialManager
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.UserChangesModel import UserChangesModel
|
||||
from cura.Settings.ExtrudersModel import ExtrudersModel
|
||||
@ -138,13 +141,14 @@ class CuraApplication(QtApplication):
|
||||
# From which stack the setting would inherit if not defined per object (handled in the engine)
|
||||
# AND for settings which are not settable_per_mesh:
|
||||
# which extruder is the only extruder this setting is obtained from
|
||||
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1")
|
||||
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1", depends_on = "value")
|
||||
|
||||
# For settings which are not settable_per_mesh and not settable_per_extruder:
|
||||
# A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders
|
||||
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None, depends_on = "value")
|
||||
|
||||
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
||||
SettingDefinition.addSettingType("optional_extruder", None, str, None)
|
||||
|
||||
SettingDefinition.addSettingType("[int]", None, str, None)
|
||||
|
||||
@ -178,7 +182,8 @@ class CuraApplication(QtApplication):
|
||||
("machine_stack", ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
|
||||
("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
|
||||
("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
|
||||
("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
|
||||
("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
|
||||
("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"),
|
||||
}
|
||||
)
|
||||
|
||||
@ -187,6 +192,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||
self._machine_manager = None # This is initialized on demand.
|
||||
self._material_manager = None
|
||||
self._setting_inheritance_manager = None
|
||||
|
||||
self._additional_components = {} # Components to add to certain areas in the interface
|
||||
@ -266,7 +272,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
Preferences.getInstance().addPreference("cura/categories_expanded", "")
|
||||
Preferences.getInstance().addPreference("cura/jobname_prefix", True)
|
||||
Preferences.getInstance().addPreference("view/center_on_select", False)
|
||||
Preferences.getInstance().addPreference("view/center_on_select", True)
|
||||
Preferences.getInstance().addPreference("mesh/scale_to_fit", False)
|
||||
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
|
||||
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
|
||||
@ -320,7 +326,6 @@ class CuraApplication(QtApplication):
|
||||
support_enable
|
||||
support_extruder_nr
|
||||
support_type
|
||||
support_interface_density
|
||||
platform_adhesion
|
||||
adhesion_type
|
||||
adhesion_extruder_nr
|
||||
@ -347,6 +352,8 @@ class CuraApplication(QtApplication):
|
||||
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
self._plugin_registry.addSupportedPluginExtension("curaplugin", "Cura Plugin")
|
||||
|
||||
def _onEngineCreated(self):
|
||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||
|
||||
@ -488,7 +495,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
self._plugin_registry.loadPlugins()
|
||||
|
||||
if self.getBackend() == None:
|
||||
if self.getBackend() is None:
|
||||
raise RuntimeError("Could not load the backend plugin!")
|
||||
|
||||
self._plugins_loaded = True
|
||||
@ -637,6 +644,7 @@ class CuraApplication(QtApplication):
|
||||
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
|
||||
ExtruderManager.getInstance()
|
||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
|
||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
|
||||
self.getSettingInheritanceManager)
|
||||
|
||||
@ -662,6 +670,11 @@ class CuraApplication(QtApplication):
|
||||
self._machine_manager = MachineManager.createMachineManager()
|
||||
return self._machine_manager
|
||||
|
||||
def getMaterialManager(self, *args):
|
||||
if self._material_manager is None:
|
||||
self._material_manager = MaterialManager.createMaterialManager()
|
||||
return self._material_manager
|
||||
|
||||
def getSettingInheritanceManager(self, *args):
|
||||
if self._setting_inheritance_manager is None:
|
||||
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
||||
@ -705,6 +718,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
||||
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
|
||||
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
|
||||
qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
|
||||
qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
|
||||
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||
@ -748,8 +762,7 @@ class CuraApplication(QtApplication):
|
||||
# Default
|
||||
self.getController().setActiveTool("TranslateTool")
|
||||
|
||||
# Hack: QVector bindings are broken on PyQt 5.7.1 on Windows. This disables it being called at all.
|
||||
if Preferences.getInstance().getValue("view/center_on_select") and not Platform.isWindows():
|
||||
if Preferences.getInstance().getValue("view/center_on_select"):
|
||||
self._center_after_select = True
|
||||
else:
|
||||
if self.getController().getActiveTool():
|
||||
|
@ -69,7 +69,7 @@ class LayerDataBuilder(MeshBuilder):
|
||||
|
||||
vertex_offset = 0
|
||||
index_offset = 0
|
||||
for layer, data in self._layers.items():
|
||||
for layer, data in sorted(self._layers.items()):
|
||||
( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices)
|
||||
self._element_counts[layer] = data.elementCount
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
from UM.Math.Color import Color
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from typing import Any
|
||||
import numpy
|
||||
@ -16,8 +18,9 @@ class LayerPolygon:
|
||||
MoveCombingType = 8
|
||||
MoveRetractionType = 9
|
||||
SupportInterfaceType = 10
|
||||
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(11) == NoneType, numpy.arange(11) == MoveCombingType), numpy.arange(11) == MoveRetractionType)
|
||||
__number_of_types = 11
|
||||
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
||||
|
||||
## LayerPolygon, used in ProcessSlicedLayersJob
|
||||
# \param extruder
|
||||
@ -28,6 +31,9 @@ class LayerPolygon:
|
||||
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses):
|
||||
self._extruder = extruder
|
||||
self._types = line_types
|
||||
for i in range(len(self._types)):
|
||||
if self._types[i] >= self.__number_of_types: #Got faulty line data from the engine.
|
||||
self._types[i] = self.NoneType
|
||||
self._data = data
|
||||
self._line_widths = line_widths
|
||||
self._line_thicknesses = line_thicknesses
|
||||
@ -36,11 +42,11 @@ class LayerPolygon:
|
||||
self._vertex_end = 0
|
||||
self._index_begin = 0
|
||||
self._index_end = 0
|
||||
|
||||
|
||||
self._jump_mask = self.__jump_map[self._types]
|
||||
self._jump_count = numpy.sum(self._jump_mask)
|
||||
self._mesh_line_count = len(self._types)-self._jump_count
|
||||
self._vertex_count = self._mesh_line_count + numpy.sum( self._types[1:] == self._types[:-1])
|
||||
self._mesh_line_count = len(self._types) - self._jump_count
|
||||
self._vertex_count = self._mesh_line_count + numpy.sum(self._types[1:] == self._types[:-1])
|
||||
|
||||
# Buffering the colors shouldn't be necessary as it is not
|
||||
# re-used and can save alot of memory usage.
|
||||
|
@ -23,8 +23,8 @@ class PlatformPhysicsOperation(Operation):
|
||||
def mergeWith(self, other):
|
||||
group = GroupedOperation()
|
||||
|
||||
group.addOperation(self)
|
||||
group.addOperation(other)
|
||||
group.addOperation(self)
|
||||
|
||||
return group
|
||||
|
||||
|
@ -3,13 +3,18 @@
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtSignal, QUrl
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from enum import IntEnum # For the connection state tracking.
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import signalemitter
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Application import Application
|
||||
|
||||
import os
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
@ -57,6 +62,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
|
||||
self._camera_active = False
|
||||
|
||||
self._monitor_view_qml_path = ""
|
||||
self._monitor_component = None
|
||||
self._monitor_item = None
|
||||
self._qml_context = None
|
||||
|
||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
||||
@ -111,6 +121,32 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
# Signal to be emitted when some drastic change occurs in the remaining time (not when the time just passes on normally).
|
||||
preheatBedRemainingTimeChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
def monitorItem(self):
|
||||
# Note that we specifically only check if the monitor component is created.
|
||||
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
|
||||
# create the item (and fail) every time.
|
||||
if not self._monitor_component:
|
||||
self._createMonitorViewFromQML()
|
||||
|
||||
return self._monitor_item
|
||||
|
||||
def _createMonitorViewFromQML(self):
|
||||
path = QUrl.fromLocalFile(self._monitor_view_qml_path)
|
||||
|
||||
# Because of garbage collection we need to keep this referenced by python.
|
||||
self._monitor_component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
# Check if the context was already requested before (Printer output device might have multiple items in the future)
|
||||
if self._qml_context is None:
|
||||
self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._qml_context.setContextProperty("OutputDevice", self)
|
||||
|
||||
self._monitor_item = self._monitor_component.create(self._qml_context)
|
||||
if self._monitor_item is None:
|
||||
Logger.log("e", "QQmlComponent status %s", self._monitor_component.status())
|
||||
Logger.log("e", "QQmlComponent error string %s", self._monitor_component.errorString())
|
||||
|
||||
@pyqtProperty(str, notify=printerTypeChanged)
|
||||
def printerType(self):
|
||||
return self._printer_type
|
||||
|
@ -54,9 +54,16 @@ class QualityManager:
|
||||
# specified then the currently selected machine definition is used..
|
||||
# \return the matching quality changes containers \type{List[InstanceContainer]}
|
||||
def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
|
||||
criteria = {"type": "quality_changes", "name": quality_changes_name}
|
||||
result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
|
||||
if not machine_definition:
|
||||
global_stack = Application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return [] #No stack, so no current definition could be found, so there are no quality changes either.
|
||||
machine_definition = global_stack.definition
|
||||
|
||||
result = self.findAllQualityChangesForMachine(machine_definition)
|
||||
for extruder in self.findAllExtruderDefinitionsForMachine(machine_definition):
|
||||
result.extend(self.findAllQualityChangesForExtruder(extruder))
|
||||
result = [quality_change for quality_change in result if quality_change.getName() == quality_changes_name]
|
||||
return result
|
||||
|
||||
## Fetch the list of available quality types for this combination of machine definition and materials.
|
||||
@ -101,12 +108,11 @@ class QualityManager:
|
||||
if quality_type:
|
||||
criteria["quality_type"] = quality_type
|
||||
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
|
||||
|
||||
# Fall back to using generic materials and qualities if nothing could be found.
|
||||
if not result and material_containers and len(material_containers) == 1:
|
||||
basic_materials = self._getBasicMaterials(material_containers[0])
|
||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||
|
||||
if basic_materials:
|
||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||
return result[0] if result else None
|
||||
|
||||
## Find all suitable qualities for a combination of machine and material.
|
||||
@ -119,7 +125,8 @@ class QualityManager:
|
||||
result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
|
||||
if not result:
|
||||
basic_materials = self._getBasicMaterials(material_container)
|
||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||
if basic_materials:
|
||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
||||
|
||||
return result
|
||||
|
||||
@ -137,6 +144,18 @@ class QualityManager:
|
||||
quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
||||
return quality_changes_list
|
||||
|
||||
def findAllExtruderDefinitionsForMachine(self, machine_definition: "DefinitionContainerInterface") -> List["DefinitionContainerInterface"]:
|
||||
filter_dict = { "machine": machine_definition.getId() }
|
||||
return ContainerRegistry.getInstance().findDefinitionContainers(**filter_dict)
|
||||
|
||||
## Find all quality changes for a given extruder.
|
||||
#
|
||||
# \param extruder_definition The extruder to find the quality changes for.
|
||||
# \return The list of quality changes for the given extruder.
|
||||
def findAllQualityChangesForExtruder(self, extruder_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
|
||||
filter_dict = {"type": "quality_changes", "extruder": extruder_definition.getId()}
|
||||
return ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
||||
|
||||
## Find all usable qualities for a machine and extruders.
|
||||
#
|
||||
# Finds all of the qualities for this combination of machine and extruders.
|
||||
@ -179,7 +198,6 @@ class QualityManager:
|
||||
definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
|
||||
else:
|
||||
definition_id = "fdmprinter"
|
||||
|
||||
if base_material:
|
||||
# There is a basic material specified
|
||||
criteria = { "type": "material", "name": base_material, "definition": definition_id }
|
||||
@ -202,7 +220,11 @@ class QualityManager:
|
||||
if quality_definition_id is not None:
|
||||
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0]
|
||||
|
||||
# for convenience
|
||||
if material_containers is None:
|
||||
material_containers = []
|
||||
|
||||
if not material_containers:
|
||||
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
||||
if active_stacks:
|
||||
material_containers = [stack.material for stack in active_stacks]
|
||||
@ -219,26 +241,25 @@ class QualityManager:
|
||||
filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
|
||||
else:
|
||||
criteria["definition"] = "fdmprinter"
|
||||
material_ids = set()
|
||||
# Stick the material IDs in a set
|
||||
if material_containers is None or len(material_containers) == 0:
|
||||
filter_by_material = False
|
||||
else:
|
||||
for material_instance in material_containers:
|
||||
if material_instance is not None:
|
||||
# Add the parent material too.
|
||||
for basic_material in self._getBasicMaterials(material_instance):
|
||||
material_ids.add(basic_material.getId())
|
||||
material_ids.add(material_instance.getId())
|
||||
|
||||
# Stick the material IDs in a set
|
||||
material_ids = set()
|
||||
|
||||
for material_instance in material_containers:
|
||||
if material_instance is not None:
|
||||
# Add the parent material too.
|
||||
for basic_material in self._getBasicMaterials(material_instance):
|
||||
material_ids.add(basic_material.getId())
|
||||
material_ids.add(material_instance.getId())
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||
|
||||
result = []
|
||||
for container in containers:
|
||||
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
|
||||
if filter_by_material and container.getMetaDataEntry("material") not in material_ids and not "global_quality" in kwargs:
|
||||
if filter_by_material and container.getMetaDataEntry("material") not in material_ids and "global_quality" not in kwargs:
|
||||
continue
|
||||
result.append(container)
|
||||
|
||||
return result
|
||||
|
||||
## Get the parent machine definition of a machine definition.
|
||||
|
@ -235,6 +235,63 @@ class ContainerManager(QObject):
|
||||
|
||||
return True
|
||||
|
||||
## Set a setting property of the specified container.
|
||||
#
|
||||
# This will set the specified property of the specified setting of the container
|
||||
# and all containers that share the same base_file (if any). The latter only
|
||||
# happens for material containers.
|
||||
#
|
||||
# \param container_id \type{str} The ID of the container to change.
|
||||
# \param setting_key \type{str} The key of the setting.
|
||||
# \param property_name \type{str} The name of the property, eg "value".
|
||||
# \param property_value \type{str} The new value of the property.
|
||||
#
|
||||
# \return True if successful, False if not.
|
||||
@pyqtSlot(str, str, str, str, result = bool)
|
||||
def setContainerProperty(self, container_id, setting_key, property_name, property_value):
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
Logger.log("w", "Could not set properties of container %s because it was not found.", container_id)
|
||||
return False
|
||||
|
||||
container = containers[0]
|
||||
|
||||
if container.isReadOnly():
|
||||
Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
|
||||
return False
|
||||
|
||||
container.setProperty(setting_key, property_name, property_value)
|
||||
|
||||
basefile = container.getMetaDataEntry("base_file", container_id)
|
||||
for sibbling_container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
|
||||
if sibbling_container != container:
|
||||
sibbling_container.setProperty(setting_key, property_name, property_value)
|
||||
|
||||
return True
|
||||
|
||||
## Get a setting property of the specified container.
|
||||
#
|
||||
# This will get the specified property of the specified setting of the
|
||||
# specified container.
|
||||
#
|
||||
# \param container_id The ID of the container to get the setting property
|
||||
# of.
|
||||
# \param setting_key The key of the setting to get the property of.
|
||||
# \param property_name The property to obtain.
|
||||
# \return The value of the specified property. The type of this property
|
||||
# value depends on the type of the property. For instance, the "value"
|
||||
# property of an integer setting will be a Python int, but the "value"
|
||||
# property of an enum setting will be a Python str.
|
||||
@pyqtSlot(str, str, str, result = QVariant)
|
||||
def getContainerProperty(self, container_id: str, setting_key: str, property_name: str):
|
||||
containers = self._container_registry.findContainers(id = container_id)
|
||||
if not containers:
|
||||
Logger.log("w", "Could not get properties of container %s because it was not found.", container_id)
|
||||
return ""
|
||||
container = containers[0]
|
||||
|
||||
return container.getProperty(setting_key, property_name)
|
||||
|
||||
## Set the name of the specified container.
|
||||
@pyqtSlot(str, str, result = bool)
|
||||
def setContainerName(self, container_id, new_name):
|
||||
@ -527,7 +584,7 @@ class ContainerManager(QObject):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack or not quality_name:
|
||||
return ""
|
||||
machine_definition = global_stack.getBottom()
|
||||
machine_definition = QualityManager.getInstance().getParentMachineDefinition(global_stack.getBottom())
|
||||
|
||||
for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
|
||||
containers_found = True
|
||||
@ -712,7 +769,7 @@ class ContainerManager(QObject):
|
||||
if not global_stack:
|
||||
return ""
|
||||
|
||||
approximate_diameter = round(global_stack.getProperty("material_diameter", "value"))
|
||||
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
|
||||
containers = self._container_registry.findInstanceContainers(id = "generic_pla*", approximate_diameter = approximate_diameter)
|
||||
if not containers:
|
||||
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
|
||||
@ -737,7 +794,9 @@ class ContainerManager(QObject):
|
||||
|
||||
duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
|
||||
duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
|
||||
duplicated_container.setMetaDataEntry("material", catalog.i18nc("@label", "Custom"))
|
||||
# We're defaulting to PLA, as machines with material profiles don't like material types they don't know.
|
||||
# TODO: This is a hack, the only reason this is in now is to bandaid the problem as we're close to a release!
|
||||
duplicated_container.setMetaDataEntry("material", "PLA")
|
||||
duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
|
||||
|
||||
self._container_registry.addContainer(duplicated_container)
|
||||
@ -929,6 +988,7 @@ class ContainerManager(QObject):
|
||||
quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
|
||||
else:
|
||||
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||
return quality_changes
|
||||
|
@ -4,6 +4,9 @@
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from UM.Decorators import override
|
||||
@ -199,8 +202,12 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
new_name = self.uniqueName(name_seed)
|
||||
if type(profile_or_list) is not list:
|
||||
profile = profile_or_list
|
||||
self._configureProfile(profile, name_seed, new_name)
|
||||
return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
|
||||
|
||||
result = self._configureProfile(profile, name_seed, new_name)
|
||||
if result is not None:
|
||||
return {"status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result)}
|
||||
|
||||
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName())}
|
||||
else:
|
||||
profile_index = -1
|
||||
global_profile = None
|
||||
@ -230,7 +237,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
global_profile = profile
|
||||
profile_id = (global_container_stack.getBottom().getId() + "_" + name_seed).lower().replace(" ", "_")
|
||||
|
||||
self._configureProfile(profile, profile_id, new_name)
|
||||
result = self._configureProfile(profile, profile_id, new_name)
|
||||
if result is not None:
|
||||
return {"status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, result)}
|
||||
|
||||
profile_index += 1
|
||||
|
||||
@ -244,7 +253,14 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
super().load()
|
||||
self._fixupExtruders()
|
||||
|
||||
def _configureProfile(self, profile, id_seed, new_name):
|
||||
## Update an imported profile to match the current machine configuration.
|
||||
#
|
||||
# \param profile The profile to configure.
|
||||
# \param id_seed The base ID for the profile. May be changed so it does not conflict with existing containers.
|
||||
# \param new_name The new name for the profile.
|
||||
#
|
||||
# \return None if configuring was successful or an error message if an error occurred.
|
||||
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]:
|
||||
profile.setReadOnly(False)
|
||||
profile.setDirty(True) # Ensure the profiles are correctly saved
|
||||
|
||||
@ -257,15 +273,36 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
else:
|
||||
profile.addMetaDataEntry("type", "quality_changes")
|
||||
|
||||
quality_type = profile.getMetaDataEntry("quality_type")
|
||||
if not quality_type:
|
||||
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||
|
||||
quality_type_criteria = {"quality_type": quality_type}
|
||||
if self._machineHasOwnQualities():
|
||||
profile.setDefinition(self._activeQualityDefinition())
|
||||
if self._machineHasOwnMaterials():
|
||||
profile.addMetaDataEntry("material", self._activeMaterialId())
|
||||
active_material_id = self._activeMaterialId()
|
||||
if active_material_id: # only update if there is an active material
|
||||
profile.addMetaDataEntry("material", active_material_id)
|
||||
quality_type_criteria["material"] = active_material_id
|
||||
|
||||
quality_type_criteria["definition"] = profile.getDefinition().getId()
|
||||
|
||||
else:
|
||||
profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
|
||||
quality_type_criteria["definition"] = "fdmprinter"
|
||||
|
||||
# Check to make sure the imported profile actually makes sense in context of the current configuration.
|
||||
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
|
||||
# successfully imported but then fail to show up.
|
||||
qualities = self.findInstanceContainers(**quality_type_criteria)
|
||||
if not qualities:
|
||||
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
|
||||
|
||||
ContainerRegistry.getInstance().addContainer(profile)
|
||||
|
||||
return None
|
||||
|
||||
## Gets a list of profile writer plugins
|
||||
# \return List of tuples of (plugin_id, meta_data).
|
||||
def _getIOPlugins(self, io_type):
|
||||
|
@ -5,7 +5,7 @@ import os.path
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
||||
from UM.Decorators import override
|
||||
@ -250,7 +250,7 @@ class CuraContainerStack(ContainerStack):
|
||||
## Get the definition container.
|
||||
#
|
||||
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(DefinitionContainer, fset = setDefinition, notify = pyqtContainersChanged)
|
||||
@pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
|
||||
def definition(self) -> DefinitionContainer:
|
||||
return self._containers[_ContainerIndexes.Definition]
|
||||
|
||||
|
@ -30,6 +30,11 @@ class CuraStackBuilder:
|
||||
|
||||
machine_definition = definitions[0]
|
||||
name = registry.createUniqueName("machine", "", name, machine_definition.name)
|
||||
# Make sure the new name does not collide with any definition or (quality) profile
|
||||
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles
|
||||
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
|
||||
if registry.findContainers(id = name):
|
||||
name = registry.uniqueName(name)
|
||||
|
||||
new_global_stack = cls.createGlobalStack(
|
||||
new_stack_id = name,
|
||||
|
@ -15,7 +15,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||
from typing import Optional, List, TYPE_CHECKING, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -74,12 +74,13 @@ class ExtruderManager(QObject):
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
@pyqtProperty("QVariantMap", notify=extrudersChanged)
|
||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||
def extruderIds(self):
|
||||
map = {}
|
||||
global_stack_id = Application.getInstance().getGlobalContainerStack().getId()
|
||||
for position in self._extruder_trains[global_stack_id]:
|
||||
map[position] = self._extruder_trains[global_stack_id][position].getId()
|
||||
if global_stack_id in self._extruder_trains:
|
||||
for position in self._extruder_trains[global_stack_id]:
|
||||
map[position] = self._extruder_trains[global_stack_id][position].getId()
|
||||
return map
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
@ -151,14 +152,14 @@ class ExtruderManager(QObject):
|
||||
selected_nodes.append(node)
|
||||
|
||||
# Then, figure out which nodes are used by those selected nodes.
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
current_extruder_trains = self._extruder_trains.get(global_stack.getId())
|
||||
for node in selected_nodes:
|
||||
extruder = node.callDecoration("getActiveExtruder")
|
||||
if extruder:
|
||||
object_extruders.add(extruder)
|
||||
else:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack.getId() in self._extruder_trains:
|
||||
object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId())
|
||||
elif current_extruder_trains:
|
||||
object_extruders.add(current_extruder_trains["0"].getId())
|
||||
|
||||
self._selected_object_extruders = list(object_extruders)
|
||||
|
||||
@ -203,7 +204,7 @@ class ExtruderManager(QObject):
|
||||
# \param machine_definition The machine definition to add the extruders for.
|
||||
# \param machine_id The machine_id to add the extruders for.
|
||||
@deprecated("Use CuraStackBuilder", "2.6")
|
||||
def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None:
|
||||
def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None:
|
||||
changed = False
|
||||
machine_definition_id = machine_definition.getId()
|
||||
if machine_id not in self._extruder_trains:
|
||||
@ -263,7 +264,7 @@ class ExtruderManager(QObject):
|
||||
# \param position The position of this extruder train in the extruder slots of the machine.
|
||||
# \param machine_id The id of the "global" stack this extruder is linked to.
|
||||
@deprecated("Use CuraStackBuilder::createExtruderStack", "2.6")
|
||||
def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer,
|
||||
def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface,
|
||||
position, machine_id: str) -> None:
|
||||
# Cache some things.
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
@ -310,9 +311,9 @@ class ExtruderManager(QObject):
|
||||
if preferred_material_id:
|
||||
global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||
if global_stack:
|
||||
approximate_material_diameter = round(global_stack[0].getProperty("material_diameter", "value"))
|
||||
approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value")))
|
||||
else:
|
||||
approximate_material_diameter = round(machine_definition.getProperty("material_diameter", "value"))
|
||||
approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value")))
|
||||
|
||||
search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter}
|
||||
if machine_definition.getMetaDataEntry("has_machine_materials"):
|
||||
@ -470,7 +471,8 @@ class ExtruderManager(QObject):
|
||||
for extruder in self.getMachineExtruders(machine_id):
|
||||
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
|
||||
ContainerRegistry.getInstance().removeContainer(extruder.getId())
|
||||
del self._extruder_trains[machine_id]
|
||||
if machine_id in self._extruder_trains:
|
||||
del self._extruder_trains[machine_id]
|
||||
|
||||
## Returns extruders for a specific machine.
|
||||
#
|
||||
@ -524,7 +526,7 @@ class ExtruderManager(QObject):
|
||||
#
|
||||
# This is exposed to SettingFunction so it can be used in value functions.
|
||||
#
|
||||
# \param key The key of the setting to retieve values for.
|
||||
# \param key The key of the setting to retrieve values for.
|
||||
#
|
||||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||
# If no extruder has the value, the list will contain the global value.
|
||||
|
@ -64,9 +64,10 @@ class ExtruderStack(CuraContainerStack):
|
||||
|
||||
limit_to_extruder = super().getProperty(key, "limit_to_extruder")
|
||||
if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
|
||||
result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name)
|
||||
if result is not None:
|
||||
return result
|
||||
if str(limit_to_extruder) in self.getNextStack().extruders:
|
||||
result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return super().getProperty(key, property_name)
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
|
||||
from typing import Iterable
|
||||
|
||||
import UM.Qt.ListModel
|
||||
from UM.Application import Application
|
||||
import UM.FlameProfiler
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ExtruderStack import ExtruderStack #To listen to changes on the extruders.
|
||||
from cura.Settings.MachineManager import MachineManager #To listen to changes on the extruders of the currently active machine.
|
||||
|
||||
## Model that holds extruders.
|
||||
#
|
||||
@ -66,16 +69,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
self._add_global = False
|
||||
self._simple_names = False
|
||||
|
||||
self._active_extruder_stack = None
|
||||
self._active_machine_extruders = [] # type: Iterable[ExtruderStack]
|
||||
self._add_optional_extruder = False
|
||||
|
||||
#Listen to changes.
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._updateExtruders)
|
||||
manager = ExtruderManager.getInstance()
|
||||
|
||||
self._updateExtruders()
|
||||
|
||||
manager.activeExtruderChanged.connect(self._onActiveExtruderChanged)
|
||||
self._onActiveExtruderChanged()
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) #When the machine is swapped we must update the active machine extruders.
|
||||
ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) #When the extruders change we must link to the stack-changed signal of the new extruder.
|
||||
self._extrudersChanged() #Also calls _updateExtruders.
|
||||
|
||||
def setAddGlobal(self, add):
|
||||
if add != self._add_global:
|
||||
@ -89,6 +89,18 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
def addGlobal(self):
|
||||
return self._add_global
|
||||
|
||||
addOptionalExtruderChanged = pyqtSignal()
|
||||
|
||||
def setAddOptionalExtruder(self, add_optional_extruder):
|
||||
if add_optional_extruder != self._add_optional_extruder:
|
||||
self._add_optional_extruder = add_optional_extruder
|
||||
self.addOptionalExtruderChanged.emit()
|
||||
self._updateExtruders()
|
||||
|
||||
@pyqtProperty(bool, fset = setAddOptionalExtruder, notify = addOptionalExtruderChanged)
|
||||
def addOptionalExtruder(self):
|
||||
return self._add_optional_extruder
|
||||
|
||||
## Set the simpleNames property.
|
||||
def setSimpleNames(self, simple_names):
|
||||
if simple_names != self._simple_names:
|
||||
@ -104,17 +116,31 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
def simpleNames(self):
|
||||
return self._simple_names
|
||||
|
||||
def _onActiveExtruderChanged(self):
|
||||
manager = ExtruderManager.getInstance()
|
||||
active_extruder_stack = manager.getActiveExtruderStack()
|
||||
if self._active_extruder_stack != active_extruder_stack:
|
||||
if self._active_extruder_stack:
|
||||
self._active_extruder_stack.containersChanged.disconnect(self._onExtruderStackContainersChanged)
|
||||
## Links to the stack-changed signal of the new extruders when an extruder
|
||||
# is swapped out or added in the current machine.
|
||||
#
|
||||
# \param machine_id The machine for which the extruders changed. This is
|
||||
# filled by the ExtruderManager.extrudersChanged signal when coming from
|
||||
# that signal. Application.globalContainerStackChanged doesn't fill this
|
||||
# signal; it's assumed to be the current printer in that case.
|
||||
def _extrudersChanged(self, machine_id = None):
|
||||
if machine_id is not None:
|
||||
if Application.getInstance().getGlobalContainerStack() is None:
|
||||
return #No machine, don't need to update the current machine's extruders.
|
||||
if machine_id != Application.getInstance().getGlobalContainerStack().getId():
|
||||
return #Not the current machine.
|
||||
#Unlink from old extruders.
|
||||
for extruder in self._active_machine_extruders:
|
||||
extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged)
|
||||
|
||||
if active_extruder_stack:
|
||||
# Update the model when the material container is changed
|
||||
active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||
self._active_extruder_stack = active_extruder_stack
|
||||
#Link to new extruders.
|
||||
self._active_machine_extruders = []
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
for extruder in extruder_manager.getExtruderStacks():
|
||||
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||
self._active_machine_extruders.append(extruder)
|
||||
|
||||
self._updateExtruders() #Since the new extruders may have different properties, update our own model.
|
||||
|
||||
def _onExtruderStackContainersChanged(self, container):
|
||||
# Update when there is an empty container or material change
|
||||
@ -184,5 +210,16 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
|
||||
if changed:
|
||||
items.sort(key = lambda i: i["index"])
|
||||
# We need optional extruder to be last, so add it after we do sorting.
|
||||
# This way we can simply intrepret the -1 of the index as the last item (which it now always is)
|
||||
if self._add_optional_extruder:
|
||||
item = {
|
||||
"id": "",
|
||||
"name": "Not overridden",
|
||||
"color": "#ffffff",
|
||||
"index": -1,
|
||||
"definition": ""
|
||||
}
|
||||
items.append(item)
|
||||
self.setItems(items)
|
||||
self.modelChanged.emit()
|
||||
|
@ -107,6 +107,21 @@ class GlobalStack(CuraContainerStack):
|
||||
def setNextStack(self, next_stack: ContainerStack) -> None:
|
||||
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
|
||||
|
||||
## Gets the approximate filament diameter that the machine requires.
|
||||
#
|
||||
# The approximate material diameter is the material diameter rounded to
|
||||
# the nearest millimetre.
|
||||
#
|
||||
# If the machine has no requirement for the diameter, -1 is returned.
|
||||
#
|
||||
# \return The approximate filament diameter for the printer, as a string.
|
||||
@pyqtProperty(str)
|
||||
def approximateMaterialDiameter(self) -> str:
|
||||
material_diameter = self.definition.getProperty("material_diameter", "value")
|
||||
if material_diameter is None:
|
||||
return "-1"
|
||||
return str(round(float(material_diameter))) #Round, then convert back to string.
|
||||
|
||||
# protected:
|
||||
|
||||
# Determine whether or not we should try to get the "resolve" property instead of the
|
||||
|
@ -11,12 +11,13 @@ from UM.Application import Application
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Decorators import deprecated
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Signal import postponeSignals
|
||||
from UM.Signal import postponeSignals, CompressTechnique
|
||||
import UM.FlameProfiler
|
||||
|
||||
from cura.QualityManager import QualityManager
|
||||
@ -91,7 +92,7 @@ class MachineManager(QObject):
|
||||
self._printer_output_devices = []
|
||||
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||
|
||||
if active_machine_id != "":
|
||||
if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacks(id = active_machine_id):
|
||||
# An active machine was saved, so restore it.
|
||||
self.setActiveMachine(active_machine_id)
|
||||
if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
@ -220,6 +221,7 @@ class MachineManager(QObject):
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
self._auto_materials_changed = {} #Processed all of them now.
|
||||
|
||||
def _autoUpdateHotends(self):
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
@ -236,6 +238,7 @@ class MachineManager(QObject):
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
self._auto_hotends_changed = {} #Processed all of them now.
|
||||
|
||||
def _onGlobalContainerChanged(self):
|
||||
if self._global_container_stack:
|
||||
@ -468,7 +471,7 @@ class MachineManager(QObject):
|
||||
|
||||
return ""
|
||||
|
||||
@pyqtProperty("QObject", notify = globalContainerChanged)
|
||||
@pyqtProperty(QObject, notify = globalContainerChanged)
|
||||
def activeMachine(self) -> "GlobalStack":
|
||||
return self._global_container_stack
|
||||
|
||||
@ -703,7 +706,7 @@ class MachineManager(QObject):
|
||||
# Depending on from/to material+current variant, a quality profile is chosen and set.
|
||||
@pyqtSlot(str)
|
||||
def setActiveMaterial(self, material_id: str):
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
|
||||
if not containers or not self._active_container_stack:
|
||||
return
|
||||
@ -751,11 +754,12 @@ class MachineManager(QObject):
|
||||
candidate_quality = quality_manager.findQualityByQualityType(quality_type,
|
||||
quality_manager.getWholeMachineDefinition(machine_definition),
|
||||
[material_container])
|
||||
|
||||
if not candidate_quality or isinstance(candidate_quality, type(self._empty_quality_changes_container)):
|
||||
Logger.log("d", "Attempting to find fallback quality")
|
||||
# Fall back to a quality (which must be compatible with all other extruders)
|
||||
new_qualities = quality_manager.findAllUsableQualitiesForMachineAndExtruders(
|
||||
self._global_container_stack, ExtruderManager.getInstance().getExtruderStacks())
|
||||
|
||||
if new_qualities:
|
||||
new_quality_id = new_qualities[0].getId() # Just pick the first available one
|
||||
else:
|
||||
@ -768,7 +772,7 @@ class MachineManager(QObject):
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveVariant(self, variant_id: str):
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
|
||||
if not containers or not self._active_container_stack:
|
||||
return
|
||||
@ -779,7 +783,7 @@ class MachineManager(QObject):
|
||||
self.blurSettings.emit()
|
||||
self._active_container_stack.variant = containers[0]
|
||||
Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
|
||||
preferred_material = None
|
||||
preferred_material_name = None
|
||||
if old_material:
|
||||
preferred_material_name = old_material.getName()
|
||||
|
||||
@ -791,7 +795,7 @@ class MachineManager(QObject):
|
||||
# \param quality_id The quality_id of either a quality or a quality_changes
|
||||
@pyqtSlot(str)
|
||||
def setActiveQuality(self, quality_id: str):
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self.blurSettings.emit()
|
||||
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = quality_id)
|
||||
@ -1118,11 +1122,12 @@ class MachineManager(QObject):
|
||||
def createMachineManager(engine=None, script_engine=None):
|
||||
return MachineManager()
|
||||
|
||||
@deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7")
|
||||
def _updateMaterialContainer(self, definition: "DefinitionContainer", stack: "ContainerStack", variant_container: Optional["InstanceContainer"] = None, preferred_material_name: Optional[str] = None):
|
||||
if not definition.getMetaDataEntry("has_materials"):
|
||||
return self._empty_material_container
|
||||
|
||||
approximate_material_diameter = round(stack.getProperty("material_diameter", "value"))
|
||||
approximate_material_diameter = str(round(stack.getProperty("material_diameter", "value")))
|
||||
search_criteria = { "type": "material", "approximate_diameter": approximate_material_diameter }
|
||||
|
||||
if definition.getMetaDataEntry("has_machine_materials"):
|
||||
|
54
cura/Settings/MaterialManager.py
Normal file
54
cura/Settings/MaterialManager.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot #To expose data to QML.
|
||||
|
||||
from cura.Settings.ContainerManager import ContainerManager
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message #To create a warning message about material diameter.
|
||||
from UM.i18n import i18nCatalog #Translated strings.
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
## Handles material-related data, processing requests to change them and
|
||||
# providing data for the GUI.
|
||||
#
|
||||
# TODO: Move material-related managing over from the machine manager to here.
|
||||
class MaterialManager(QObject):
|
||||
## Creates the global values for the material manager to use.
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
#Material diameter changed warning message.
|
||||
self._material_diameter_warning_message = Message(catalog.i18nc("@info:status Has a cancel button next to it.",
|
||||
"The selected material diameter causes the material to become incompatible with the current printer."))
|
||||
self._material_diameter_warning_message.addAction("Undo", catalog.i18nc("@action:button", "Undo"), None, catalog.i18nc("@action", "Undo changing the material diameter."))
|
||||
self._material_diameter_warning_message.actionTriggered.connect(self._materialWarningMessageAction)
|
||||
|
||||
## Creates an instance of the MaterialManager.
|
||||
#
|
||||
# This should only be called by PyQt to create the singleton instance of
|
||||
# this class.
|
||||
@staticmethod
|
||||
def createMaterialManager(engine = None, script_engine = None):
|
||||
return MaterialManager()
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def showMaterialWarningMessage(self, material_id, previous_diameter):
|
||||
self._material_diameter_warning_message.previous_diameter = previous_diameter #Make sure that the undo button can properly undo the action.
|
||||
self._material_diameter_warning_message.material_id = material_id
|
||||
self._material_diameter_warning_message.show()
|
||||
|
||||
## Called when clicking "undo" on the warning dialogue for disappeared
|
||||
# materials.
|
||||
#
|
||||
# This executes the undo action, restoring the material diameter.
|
||||
#
|
||||
# \param button The identifier of the button that was pressed.
|
||||
def _materialWarningMessageAction(self, message, button):
|
||||
if button == "Undo":
|
||||
container_manager = ContainerManager.getInstance()
|
||||
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "properties/diameter", self._material_diameter_warning_message.previous_diameter)
|
||||
message.hide()
|
||||
else:
|
||||
Logger.log("w", "Unknown button action for material diameter warning message: {action}".format(action = button))
|
21
cura/Settings/MaterialsModel.py
Normal file
21
cura/Settings/MaterialsModel.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials.
|
||||
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class.
|
||||
|
||||
## A model that shows a list of currently valid materials.
|
||||
class MaterialsModel(InstanceContainersModel):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerMetaDataChanged)
|
||||
|
||||
## Called when the metadata of the container was changed.
|
||||
#
|
||||
# This makes sure that we only update when it was a material that changed.
|
||||
#
|
||||
# \param container The container whose metadata was changed.
|
||||
def _onContainerMetaDataChanged(self, container):
|
||||
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
|
||||
self._update()
|
@ -40,6 +40,6 @@ class QualityAndUserProfilesModel(ProfilesModel):
|
||||
|
||||
# Filter the quality_change by the list of available quality_types
|
||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set]
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") is None]
|
||||
|
||||
return quality_list + filtered_quality_changes
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
@ -35,7 +35,7 @@ class SettingInheritanceManager(QObject):
|
||||
## Get the keys of all children settings with an override.
|
||||
@pyqtSlot(str, result = "QStringList")
|
||||
def getChildrenKeysWithOverride(self, key):
|
||||
definitions = self._global_container_stack.getBottom().findDefinitions(key=key)
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key=key)
|
||||
if not definitions:
|
||||
Logger.log("w", "Could not find definition for key [%s]", key)
|
||||
return []
|
||||
@ -55,7 +55,7 @@ class SettingInheritanceManager(QObject):
|
||||
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
|
||||
return []
|
||||
|
||||
definitions = self._global_container_stack.getBottom().findDefinitions(key=key)
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key=key)
|
||||
if not definitions:
|
||||
Logger.log("w", "Could not find definition for key [%s] (2)", key)
|
||||
return []
|
||||
@ -93,7 +93,7 @@ class SettingInheritanceManager(QObject):
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
if (property_name == "value" or property_name == "enabled") and self._global_container_stack:
|
||||
definitions = self._global_container_stack.getBottom().findDefinitions(key = key)
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key = key)
|
||||
if not definitions:
|
||||
return
|
||||
|
||||
@ -198,6 +198,10 @@ class SettingInheritanceManager(QObject):
|
||||
def _update(self):
|
||||
self._settings_with_inheritance_warning = [] # Reset previous data.
|
||||
|
||||
# Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late.
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
|
||||
# Check all setting keys that we know of and see if they are overridden.
|
||||
for setting_key in self._global_container_stack.getAllKeys():
|
||||
override = self._settingIsOverwritingInheritance(setting_key)
|
||||
@ -205,7 +209,7 @@ class SettingInheritanceManager(QObject):
|
||||
self._settings_with_inheritance_warning.append(setting_key)
|
||||
|
||||
# Check all the categories if any of their children have their inheritance overwritten.
|
||||
for category in self._global_container_stack.getBottom().findDefinitions(type = "category"):
|
||||
for category in self._global_container_stack.definition.findDefinitions(type = "category"):
|
||||
if self._recursiveCheck(category):
|
||||
self._settings_with_inheritance_warning.append(category.key)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
from UM.Application import Application
|
||||
|
||||
@ -33,4 +33,9 @@ class UserProfilesModel(ProfilesModel):
|
||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set]
|
||||
|
||||
#Only display the global quality changes.
|
||||
#Otherwise you get multiple copies of every quality changes profile.
|
||||
#The actual profile switching goes by profile name (not ID), and as long as the names are consistent, switching to any of the profiles will cause all stacks to switch.
|
||||
filtered_quality_changes = list(filter(lambda quality_changes: quality_changes.getMetaDataEntry("extruder") is None, filtered_quality_changes))
|
||||
|
||||
return filtered_quality_changes
|
||||
|
@ -3,6 +3,7 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
## A decorator that stores the amount an object has been moved below the platform.
|
||||
class ZOffsetDecorator(SceneNodeDecorator):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._z_offset = 0
|
||||
|
||||
def setZOffset(self, offset):
|
||||
|
@ -5,6 +5,7 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import faulthandler
|
||||
|
||||
from UM.Platform import Platform
|
||||
|
||||
@ -53,12 +54,14 @@ import Arcus #@UnusedImport
|
||||
import cura.CuraApplication
|
||||
import cura.Settings.CuraContainerRegistry
|
||||
|
||||
if Platform.isWindows() and hasattr(sys, "frozen"):
|
||||
if hasattr(sys, "frozen"):
|
||||
dirpath = os.path.expanduser("~/AppData/Local/cura/")
|
||||
os.makedirs(dirpath, exist_ok = True)
|
||||
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
|
||||
sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
|
||||
|
||||
faulthandler.enable()
|
||||
|
||||
# Force an instance of CuraContainerRegistry to be created and reused later.
|
||||
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance()
|
||||
|
||||
|
@ -312,11 +312,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
return WorkspaceReader.PreReadResult.accepted
|
||||
|
||||
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
|
||||
def _overrideExtruderStack(self, global_stack, extruder_index, extruder_file_content):
|
||||
extruder_index_str = str(extruder_index)
|
||||
def _overrideExtruderStack(self, global_stack, extruder_file_content):
|
||||
# get extruder position first
|
||||
extruder_config = configparser.ConfigParser()
|
||||
extruder_config.read_string(extruder_file_content)
|
||||
if not extruder_config.has_option("metadata", "position"):
|
||||
msg = "Could not find 'metadata/position' in extruder stack file"
|
||||
Logger.log("e", "Could not find 'metadata/position' in extruder stack file")
|
||||
raise RuntimeError(msg)
|
||||
extruder_position = extruder_config.get("metadata", "position")
|
||||
|
||||
extruder_stack = global_stack.extruders[extruder_index_str]
|
||||
old_extruder_stack_id = extruder_stack.getId()
|
||||
extruder_stack = global_stack.extruders[extruder_position]
|
||||
|
||||
# override the given extruder stack
|
||||
extruder_stack.deserialize(extruder_file_content)
|
||||
@ -448,13 +454,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
container_id = self._stripFileToId(instance_container_file)
|
||||
serialized = archive.open(instance_container_file).read().decode("utf-8")
|
||||
|
||||
# HACK! we ignore the "metadata/type = quality" instance containers!
|
||||
# HACK! we ignore "quality" and "variant" instance containers!
|
||||
parser = configparser.ConfigParser()
|
||||
parser.read_string(serialized)
|
||||
if not parser.has_option("metadata", "type"):
|
||||
Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
|
||||
continue
|
||||
if parser.get("metadata", "type") == "quality":
|
||||
if parser.get("metadata", "type") in self._ignored_instance_container_types:
|
||||
continue
|
||||
|
||||
instance_container = InstanceContainer(container_id)
|
||||
@ -631,7 +637,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
# NOTE: This is the same code as those in the lower part
|
||||
# deserialize new extruder stack over the current ones
|
||||
stack = self._overrideExtruderStack(global_stack, index, extruder_file_content)
|
||||
stack = self._overrideExtruderStack(global_stack, extruder_file_content)
|
||||
|
||||
elif self._resolve_strategies["machine"] == "new":
|
||||
# create a new extruder stack from this one
|
||||
@ -662,7 +668,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# No extruder stack with the same ID can be found
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
# deserialize new extruder stack over the current ones
|
||||
stack = self._overrideExtruderStack(global_stack, index, extruder_file_content)
|
||||
stack = self._overrideExtruderStack(global_stack, extruder_file_content)
|
||||
|
||||
elif self._resolve_strategies["machine"] == "new":
|
||||
# container not found, create a new one
|
||||
|
@ -12,15 +12,12 @@ UM.Dialog
|
||||
{
|
||||
title: catalog.i18nc("@title:window", "Open Project")
|
||||
|
||||
width: 550 * Screen.devicePixelRatio
|
||||
minimumWidth: 550 * Screen.devicePixelRatio
|
||||
maximumWidth: minimumWidth
|
||||
width: 500
|
||||
height: 400
|
||||
|
||||
property int comboboxHeight: 15
|
||||
property int spacerHeight: 10
|
||||
|
||||
height: 400 * Screen.devicePixelRatio
|
||||
minimumHeight: 400 * Screen.devicePixelRatio
|
||||
maximumHeight: minimumHeight
|
||||
property int comboboxHeight: 15 * Screen.devicePixelRatio
|
||||
property int spacerHeight: 10 * Screen.devicePixelRatio
|
||||
onClosing: manager.notifyClosed()
|
||||
onVisibleChanged:
|
||||
{
|
||||
@ -34,7 +31,7 @@ UM.Dialog
|
||||
Item
|
||||
{
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20 * Screen.devicePixelRatio
|
||||
anchors.margins: 20
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
@ -376,7 +373,6 @@ UM.Dialog
|
||||
enabled: true
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: ok_button.left
|
||||
anchors.bottomMargin: - 0.5 * height
|
||||
anchors.rightMargin:2
|
||||
}
|
||||
Button
|
||||
@ -384,7 +380,6 @@ UM.Dialog
|
||||
id: ok_button
|
||||
text: catalog.i18nc("@action:button","Open");
|
||||
onClicked: { manager.closeBackend(); manager.onOkButtonClicked() }
|
||||
anchors.bottomMargin: - 0.5 * height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
@ -16,21 +16,13 @@ from UM.Platform import Platform
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData() -> Dict:
|
||||
# Workarround for osx not supporting double file extensions correclty.
|
||||
# Workarround for osx not supporting double file extensions correctly.
|
||||
if Platform.isOSX():
|
||||
workspace_extension = "3mf"
|
||||
else:
|
||||
workspace_extension = "curaproject.3mf"
|
||||
|
||||
metaData = {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "3MF Reader"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides support for reading 3MF files."),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
metaData = {}
|
||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||
metaData["mesh_reader"] = [
|
||||
{
|
||||
|
8
plugins/3MFReader/plugin.json
Normal file
8
plugins/3MFReader/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "3MF Reader",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading 3MF files.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -10,19 +10,18 @@ except ImportError:
|
||||
from . import ThreeMFWorkspaceWriter
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Platform import Platform
|
||||
|
||||
i18n_catalog = i18nCatalog("uranium")
|
||||
|
||||
def getMetaData():
|
||||
metaData = {
|
||||
"plugin": {
|
||||
"name": i18n_catalog.i18nc("@label", "3MF Writer"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides support for writing 3MF files."),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
# Workarround for osx not supporting double file extensions correctly.
|
||||
if Platform.isOSX():
|
||||
workspace_extension = "3mf"
|
||||
else:
|
||||
workspace_extension = "curaproject.3mf"
|
||||
|
||||
metaData = {}
|
||||
|
||||
if "3MFWriter.ThreeMFWriter" in sys.modules:
|
||||
metaData["mesh_writer"] = {
|
||||
@ -35,7 +34,7 @@ def getMetaData():
|
||||
}
|
||||
metaData["workspace_writer"] = {
|
||||
"output": [{
|
||||
"extension": "curaproject.3mf",
|
||||
"extension": workspace_extension,
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
||||
"mime_type": "application/x-curaproject+xml",
|
||||
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
||||
|
8
plugins/3MFWriter/plugin.json
Normal file
8
plugins/3MFWriter/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "3MF Writer",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for writing 3MF files.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Auto Save"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Automatically saves Preferences, Machines and Profiles after changes."),
|
||||
"api": 3
|
||||
},
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "extension": AutoSave.AutoSave() }
|
||||
|
8
plugins/AutoSave/plugin.json
Normal file
8
plugins/AutoSave/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Auto Save",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Automatically saves Preferences, Machines and Profiles after changes.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -1,3 +1,75 @@
|
||||
[2.6.1]
|
||||
*New profiles
|
||||
The Polypropylene material is added and supported with the Ultimaker 3. Support for CPE+ and PC with 0.8mm nozzles is added as well.
|
||||
|
||||
[2.6.0]
|
||||
*Cura versions
|
||||
Cura 2.6 has local version folders, which means the new version won’t overwrite the existing configuration and profiles from older versions, but can create a new folder instead. You can now safely check out new beta versions and, if necessary, start up an older version without the danger of losing your profiles.
|
||||
|
||||
*Better support adhesion
|
||||
We’ve added extra support settings to allow the creation of improved support profiles with better PVA/PLA adhesion. The Support Interface settings, such as speed and density, are now split up into Support Roof and Support Floor settings.
|
||||
|
||||
*Multi-extrusion support for custom FDM printers
|
||||
Custom third-party printers and Ultimaker modifications now have multi-extrusion support. Thanks to Aldo Hoeben for this feature.
|
||||
|
||||
*Model auto-arrange
|
||||
We’ve improved placing multiple models or multiplying the same ones, making it easier to arrange your build plate. If there’s not enough build plate space or the model is placed beyond the build plate, you can rectify this by selecting ‘Arrange all models’ in the context menu or by pressing Command+R (MacOS) or Ctrl+R (Windows and Linux). Cura 2.6 will then find a better solution for model positioning.
|
||||
|
||||
*Gradual infill
|
||||
You can now find the Gradual Infill button in Recommended mode. This setting makes the infill concentrated near the top of the model – so that we can save time and material for the lower parts of the model. This functionality is especially useful when printing with flexible materials.
|
||||
|
||||
*Support meshes
|
||||
It’s now possible to load an extra model that will be used as a support structure.
|
||||
|
||||
*Mold
|
||||
This is a bit of an experimental improvement. Users can use it to print a mold from a 3D model, which can be cast afterwards with the material that you would like your model to have.
|
||||
|
||||
*Towers for tiny overhangs
|
||||
We’ve added a new support option allowing users to achieve more reliable results by creating towers to support even the smallest overhangs.
|
||||
|
||||
*Cutting meshes
|
||||
Easily transform any model into a dual-extrusion print by applying a pattern for the second extruder. All areas of the original model, which also fall inside the pattern model, will be printed by the extruder selected for the pattern.
|
||||
|
||||
*Extruder per model selection via the context menu or extruder buttons
|
||||
You can now select the necessary extruder in the right-click menu or extruder buttons. This is a quicker and more user-friendly process. The material color for each extruder will also be represented in the extruder icons.
|
||||
|
||||
*Custom toggle
|
||||
We have made the interface a little bit cleaner and more user-friendly for switching from Recommended to Custom mode.
|
||||
|
||||
*Plugin installer
|
||||
It used to be fairly tricky to install new plugins. We have now added a button to select and install new plugins with ease – you will find it in Preferences.
|
||||
|
||||
*Project-based menu
|
||||
It’s a lot simpler to save and open files, and Cura will know if it’s a project, model, or gcode.
|
||||
|
||||
*Theme picker
|
||||
If you have a custom theme, you can now apply it more easily in the preferences screen.
|
||||
|
||||
*Time estimates per feature
|
||||
You can hover over the print time estimate in the lower right corner to see how the printing time is divided over the printing features (walls, infill, etc.). Thanks to 14bitVoid for this feature.
|
||||
|
||||
*Invert the direction of camera zoom
|
||||
We’ve added an option to invert mouse direction for a better user experience.
|
||||
|
||||
*Olsson block upgrade
|
||||
Ultimaker 2 users can now specify if they have the Olsson block installed on their machine. Thanks to Aldo Hoeben for this feature.
|
||||
|
||||
*OctoPrint plugin
|
||||
Cura 2.6 allows users to send prints to OctoPrint. Thanks to Aldo Hoeben for this feature.
|
||||
|
||||
*Bug fixes
|
||||
- Post Processing plugin
|
||||
- Font rendering
|
||||
- Progress bar
|
||||
- Support Bottom Distance issues
|
||||
|
||||
*3rd party printers
|
||||
- MAKEIT
|
||||
- Alya
|
||||
- Peopoly Moai
|
||||
- Rigid3D Zero
|
||||
- 3D maker
|
||||
|
||||
[2.5.0]
|
||||
*Improved speed
|
||||
We’ve made changing printers, profiles, materials, and print cores even faster. 3MF processing is also much faster now. Opening a 3MF file now takes one tenth of the time.
|
||||
|
@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Changelog"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Shows changes since latest checked version."),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return {"extension": ChangeLog.ChangeLog()}
|
||||
|
8
plugins/ChangeLogPlugin/plugin.json
Normal file
8
plugins/ChangeLogPlugin/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Changelog",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Shows changes since latest checked version.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -31,6 +31,9 @@ catalog = i18nCatalog("cura")
|
||||
#
|
||||
# \param color_code html color code, i.e. "#FF0000" -> red
|
||||
def colorCodeToRGBA(color_code):
|
||||
if color_code is None:
|
||||
Logger.log("w", "Unable to convert color code, returning default")
|
||||
return [0, 0, 0, 1]
|
||||
return [
|
||||
int(color_code[1:3], 16) / 255,
|
||||
int(color_code[3:5], 16) / 255,
|
||||
@ -172,7 +175,7 @@ class ProcessSlicedLayersJob(Job):
|
||||
for extruder in extruders:
|
||||
material = extruder.findContainer({"type": "material"})
|
||||
position = int(extruder.getMetaDataEntry("position", default="0")) # Get the position
|
||||
color_code = material.getMetaDataEntry("color_code")
|
||||
color_code = material.getMetaDataEntry("color_code", default="#e0e000")
|
||||
color = colorCodeToRGBA(color_code)
|
||||
material_color_map[position, :] = color
|
||||
else:
|
||||
|
@ -149,8 +149,13 @@ class StartSliceJob(Job):
|
||||
self._buildGlobalSettingsMessage(stack)
|
||||
self._buildGlobalInheritsStackMessage(stack)
|
||||
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
self._buildExtruderMessage(extruder_stack)
|
||||
# Only add extruder stacks if there are multiple extruders
|
||||
# Single extruder machines only use the global stack to store setting values
|
||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
self._buildExtruderMessage(extruder_stack)
|
||||
else:
|
||||
self._buildExtruderMessageFromGlobalStack(stack)
|
||||
|
||||
for group in object_groups:
|
||||
group_message = self._slice_message.addRepeatedMessage("object_lists")
|
||||
@ -212,7 +217,7 @@ class StartSliceJob(Job):
|
||||
|
||||
for key in stack.getAllKeys():
|
||||
# Do not send settings that are not settable_per_extruder.
|
||||
if stack.getProperty(key, "settable_per_extruder") == False:
|
||||
if not stack.getProperty(key, "settable_per_extruder"):
|
||||
continue
|
||||
setting = message.getMessage("settings").addRepeatedMessage("settings")
|
||||
setting.name = key
|
||||
@ -223,6 +228,19 @@ class StartSliceJob(Job):
|
||||
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
|
||||
Job.yieldThread()
|
||||
|
||||
## Create extruder message from global stack
|
||||
def _buildExtruderMessageFromGlobalStack(self, stack):
|
||||
message = self._slice_message.addRepeatedMessage("extruders")
|
||||
|
||||
for key in stack.getAllKeys():
|
||||
# Do not send settings that are not settable_per_extruder.
|
||||
if not stack.getProperty(key, "settable_per_extruder"):
|
||||
continue
|
||||
setting = message.getMessage("settings").addRepeatedMessage("settings")
|
||||
setting.name = key
|
||||
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
|
||||
Job.yieldThread()
|
||||
|
||||
## Sends all global settings to the engine.
|
||||
#
|
||||
# The settings are taken from the global stack. This does not include any
|
||||
|
@ -8,14 +8,7 @@ from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "CuraEngine Backend"),
|
||||
"author": "Ultimaker",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides the link to the CuraEngine slicing backend."),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "backend": CuraEngineBackend.CuraEngineBackend() }
|
||||
|
8
plugins/CuraEngineBackend/plugin.json
Normal file
8
plugins/CuraEngineBackend/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "CuraEngine Backend",
|
||||
"author": "Ultimaker",
|
||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||
"api": 4,
|
||||
"version": "1.0.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Cura Profile Reader"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing Cura profiles."),
|
||||
"api": 3
|
||||
},
|
||||
"profile_reader": [
|
||||
{
|
||||
"extension": "curaprofile",
|
||||
|
8
plugins/CuraProfileReader/plugin.json
Normal file
8
plugins/CuraProfileReader/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Cura Profile Reader",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for importing Cura profiles.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Cura Profile Writer"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides support for exporting Cura profiles."),
|
||||
"api": 3
|
||||
},
|
||||
"profile_writer": [
|
||||
{
|
||||
"extension": "curaprofile",
|
||||
|
8
plugins/CuraProfileWriter/plugin.json
Normal file
8
plugins/CuraProfileWriter/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Cura Profile Writer",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for exporting Cura profiles.",
|
||||
"api": 4,
|
||||
"i18n-catalog":"cura"
|
||||
}
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "GCode Profile Reader"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing profiles from g-code files."),
|
||||
"api": 3
|
||||
},
|
||||
"profile_reader": [
|
||||
{
|
||||
"extension": "gcode",
|
||||
|
8
plugins/GCodeProfileReader/plugin.json
Normal file
8
plugins/GCodeProfileReader/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "GCode Profile Reader",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for importing profiles from g-code files.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -8,13 +8,6 @@ i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": i18n_catalog.i18nc("@label", "G-code Reader"),
|
||||
"author": "Victor Larchenko",
|
||||
"version": "1.0",
|
||||
"description": i18n_catalog.i18nc("@info:whatsthis", "Allows loading and displaying G-code files."),
|
||||
"api": 3
|
||||
},
|
||||
"mesh_reader": [
|
||||
{
|
||||
"extension": "gcode",
|
||||
|
8
plugins/GCodeReader/plugin.json
Normal file
8
plugins/GCodeReader/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "G-code Reader",
|
||||
"author": "Victor Larchenko",
|
||||
"version": "1.0.0",
|
||||
"description": "Allows loading and displaying G-code files.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -85,6 +85,7 @@ class GCodeWriter(MeshWriter):
|
||||
|
||||
for key in instance_container1.getAllKeys():
|
||||
flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value"))
|
||||
|
||||
return flat_container
|
||||
|
||||
|
||||
@ -106,6 +107,9 @@ class GCodeWriter(MeshWriter):
|
||||
return ""
|
||||
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
if flat_global_container.getMetaDataEntry("type", None) is None:
|
||||
flat_global_container.addMetaDataEntry("type", "quality_changes")
|
||||
|
||||
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
|
||||
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
|
||||
@ -120,6 +124,9 @@ class GCodeWriter(MeshWriter):
|
||||
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
||||
continue
|
||||
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
|
||||
# If the quality changes is not set, we need to set type manually
|
||||
if flat_extruder_quality.getMetaDataEntry("type", None) is None:
|
||||
flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
|
||||
|
||||
# Ensure that extruder is set. (Can happen if we have empty quality changes).
|
||||
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
|
||||
|
@ -8,13 +8,7 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "GCode Writer"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Writes GCode to a file."),
|
||||
"api": 3
|
||||
},
|
||||
|
||||
|
||||
"mesh_writer": {
|
||||
"output": [{
|
||||
|
8
plugins/GCodeWriter/plugin.json
Normal file
8
plugins/GCodeWriter/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "GCode Writer",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Writes GCode to a file.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -8,13 +8,6 @@ i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": i18n_catalog.i18nc("@label", "Image Reader"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": i18n_catalog.i18nc("@info:whatsthis", "Enables ability to generate printable geometry from 2D image files."),
|
||||
"api": 3
|
||||
},
|
||||
"mesh_reader": [
|
||||
{
|
||||
"extension": "jpg",
|
||||
|
8
plugins/ImageReader/plugin.json
Normal file
8
plugins/ImageReader/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Image Reader",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -82,12 +82,12 @@ class LayerPass(RenderPass):
|
||||
start = 0
|
||||
end = 0
|
||||
element_counts = layer_data.getElementCounts()
|
||||
for layer, counts in element_counts.items():
|
||||
for layer in sorted(element_counts.keys()):
|
||||
if layer > self._layer_view._current_layer_num:
|
||||
break
|
||||
if self._layer_view._minimum_layer_num > layer:
|
||||
start += counts
|
||||
end += counts
|
||||
start += element_counts[layer]
|
||||
end += element_counts[layer]
|
||||
|
||||
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
||||
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
|
||||
|
@ -11,6 +11,7 @@ import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
width: {
|
||||
if (UM.LayerView.compatibilityMode) {
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
||||
@ -25,8 +26,12 @@ Item
|
||||
return UM.Theme.getSize("layerview_menu_size").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||
}
|
||||
}
|
||||
property var buttonTarget: {
|
||||
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
||||
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
UM.PointingRectangle {
|
||||
id: layerViewMenu
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
@ -35,6 +40,9 @@ Item
|
||||
z: slider.z - 1
|
||||
color: UM.Theme.getColor("tool_panel_background")
|
||||
|
||||
target: parent.buttonTarget
|
||||
arrowSize: UM.Theme.getSize("default_arrow").width
|
||||
|
||||
ColumnLayout {
|
||||
id: view_settings
|
||||
|
||||
|
@ -9,13 +9,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Layer View"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides the Layer view."),
|
||||
"api": 3
|
||||
},
|
||||
"view": {
|
||||
"name": catalog.i18nc("@item:inlistbox", "Layers"),
|
||||
"view_panel": "LayerView.qml",
|
||||
|
@ -130,9 +130,9 @@ geometry41core =
|
||||
// fixed size for movements
|
||||
size_x = 0.05;
|
||||
} else {
|
||||
size_x = v_line_dim[0].x / 2 + 0.01; // radius, and make it nicely overlapping
|
||||
size_x = v_line_dim[1].x / 2 + 0.01; // radius, and make it nicely overlapping
|
||||
}
|
||||
size_y = v_line_dim[0].y / 2 + 0.01;
|
||||
size_y = v_line_dim[1].y / 2 + 0.01;
|
||||
|
||||
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position;
|
||||
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z));
|
||||
|
8
plugins/LayerView/plugin.json
Normal file
8
plugins/LayerView/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Layer View",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the Layer view.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Legacy Cura Profile Reader"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides support for importing profiles from legacy Cura versions."),
|
||||
"api": 3
|
||||
},
|
||||
"profile_reader": [
|
||||
{
|
||||
"extension": "ini",
|
||||
|
8
plugins/LegacyProfileReader/plugin.json
Normal file
8
plugins/LegacyProfileReader/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Legacy Cura Profile Reader",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -69,8 +69,9 @@ class MachineSettingsAction(MachineAction):
|
||||
self._container_index = container_index
|
||||
self.containerIndexChanged.emit()
|
||||
|
||||
# Disable autoslicing while the machineaction is showing
|
||||
self._backend.disableTimer()
|
||||
# Disable auto-slicing while the MachineAction is showing
|
||||
if self._backend: # This sometimes triggers before backend is loaded.
|
||||
self._backend.disableTimer()
|
||||
|
||||
@pyqtSlot()
|
||||
def onFinishAction(self):
|
||||
|
@ -7,15 +7,7 @@ from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Machine Settings action"),
|
||||
"author": "fieldOfView",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides a way to change machine settings (such as build volume, nozzle size, etc)"),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "machine_action": MachineSettingsAction.MachineSettingsAction() }
|
||||
|
8
plugins/MachineSettingsAction/plugin.json
Normal file
8
plugins/MachineSettingsAction/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Machine Settings action",
|
||||
"author": "fieldOfView",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc)",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -94,6 +94,8 @@ Item {
|
||||
return settingComboBox
|
||||
case "extruder":
|
||||
return settingExtruder
|
||||
case "optional_extruder":
|
||||
return settingOptionalExtruder
|
||||
case "bool":
|
||||
return settingCheckBox
|
||||
case "str":
|
||||
@ -342,6 +344,13 @@ Item {
|
||||
Cura.SettingExtruder { }
|
||||
}
|
||||
|
||||
Component
|
||||
{
|
||||
id: settingOptionalExtruder
|
||||
|
||||
Cura.SettingOptionalExtruder { }
|
||||
}
|
||||
|
||||
Component
|
||||
{
|
||||
id: settingCheckBox;
|
||||
|
@ -10,13 +10,6 @@ i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": i18n_catalog.i18nc("@label", "Per Model Settings Tool"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides the Per Model Settings."),
|
||||
"api": 3
|
||||
},
|
||||
"tool": {
|
||||
"name": i18n_catalog.i18nc("@label", "Per Model Settings"),
|
||||
"description": i18n_catalog.i18nc("@info:tooltip", "Configure Per Model Settings"),
|
||||
|
8
plugins/PerObjectSettingsTool/plugin.json
Normal file
8
plugins/PerObjectSettingsTool/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Per Model Settings Tool",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the Per Model Settings.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
201
plugins/PluginBrowser/PluginBrowser.py
Normal file
201
plugins/PluginBrowser/PluginBrowser.py
Normal file
@ -0,0 +1,201 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# PluginBrowser is released under the terms of the AGPLv3 or higher.
|
||||
from UM.Extension import Extension
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Application import Application
|
||||
from UM.Version import Version
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class PluginBrowser(QObject, Extension):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self.addMenuItem(i18n_catalog.i18n("Browse plugins"), self.browsePlugins)
|
||||
self._api_version = 1
|
||||
self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version
|
||||
|
||||
self._plugin_list_request = None
|
||||
self._download_plugin_request = None
|
||||
|
||||
self._download_plugin_reply = None
|
||||
|
||||
self._network_manager = None
|
||||
|
||||
self._plugins_metadata = []
|
||||
self._plugins_model = None
|
||||
|
||||
self._qml_component = None
|
||||
self._qml_context = None
|
||||
self._dialog = None
|
||||
self._download_progress = 0
|
||||
|
||||
self._is_downloading = False
|
||||
|
||||
self._request_header = [b"User-Agent", str.encode("%s - %s" % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion()))]
|
||||
|
||||
# Installed plugins are really installed after reboot. In order to prevent the user from downloading the
|
||||
# same file over and over again, we keep track of the upgraded plugins.
|
||||
self._newly_installed_plugin_ids = []
|
||||
|
||||
|
||||
pluginsMetadataChanged = pyqtSignal()
|
||||
onDownloadProgressChanged = pyqtSignal()
|
||||
onIsDownloadingChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify = onIsDownloadingChanged)
|
||||
def isDownloading(self):
|
||||
return self._is_downloading
|
||||
|
||||
def browsePlugins(self):
|
||||
self._createNetworkManager()
|
||||
self.requestPluginList()
|
||||
|
||||
if not self._dialog:
|
||||
self._createDialog()
|
||||
self._dialog.show()
|
||||
|
||||
def requestPluginList(self):
|
||||
url = QUrl(self._api_url + "plugins")
|
||||
self._plugin_list_request = QNetworkRequest(url)
|
||||
self._plugin_list_request.setRawHeader(*self._request_header)
|
||||
self._network_manager.get(self._plugin_list_request)
|
||||
|
||||
def _createDialog(self):
|
||||
Logger.log("d", "PluginBrowser")
|
||||
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "PluginBrowser.qml"))
|
||||
self._qml_component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
# We need access to engine (although technically we can't)
|
||||
self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._qml_context.setContextProperty("manager", self)
|
||||
self._dialog = self._qml_component.create(self._qml_context)
|
||||
if self._dialog is None:
|
||||
Logger.log("e", "QQmlComponent status %s", self._qml_component.status())
|
||||
Logger.log("e", "QQmlComponent errorString %s", self._qml_component.errorString())
|
||||
|
||||
def setIsDownloading(self, is_downloading):
|
||||
if self._is_downloading != is_downloading:
|
||||
self._is_downloading = is_downloading
|
||||
self.onIsDownloadingChanged.emit()
|
||||
|
||||
def _onDownloadPluginProgress(self, bytes_sent, bytes_total):
|
||||
if bytes_total > 0:
|
||||
new_progress = bytes_sent / bytes_total * 100
|
||||
if new_progress > self._download_progress:
|
||||
self._download_progress = new_progress
|
||||
self.onDownloadProgressChanged.emit()
|
||||
self._download_progress = new_progress
|
||||
if new_progress == 100.0:
|
||||
self.setIsDownloading(False)
|
||||
self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress)
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(suffix = ".curaplugin")
|
||||
self._temp_plugin_file.write(self._download_plugin_reply.readAll())
|
||||
|
||||
result = PluginRegistry.getInstance().installPlugin("file://" + self._temp_plugin_file.name)
|
||||
|
||||
self._newly_installed_plugin_ids.append(result["id"])
|
||||
self.pluginsMetadataChanged.emit()
|
||||
|
||||
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"])
|
||||
|
||||
self._temp_plugin_file.close() # Plugin was installed, delete temp file
|
||||
|
||||
@pyqtProperty(int, notify = onDownloadProgressChanged)
|
||||
def downloadProgress(self):
|
||||
return self._download_progress
|
||||
|
||||
@pyqtSlot(str)
|
||||
def downloadAndInstallPlugin(self, url):
|
||||
Logger.log("i", "Attempting to download & install plugin from %s", url)
|
||||
url = QUrl(url)
|
||||
self._download_plugin_request = QNetworkRequest(url)
|
||||
self._download_plugin_request.setRawHeader(*self._request_header)
|
||||
self._download_plugin_reply = self._network_manager.get(self._download_plugin_request)
|
||||
self._download_progress = 0
|
||||
self.setIsDownloading(True)
|
||||
self.onDownloadProgressChanged.emit()
|
||||
self._download_plugin_reply.downloadProgress.connect(self._onDownloadPluginProgress)
|
||||
|
||||
@pyqtProperty(QObject, notify=pluginsMetadataChanged)
|
||||
def pluginsModel(self):
|
||||
if self._plugins_model is None:
|
||||
self._plugins_model = ListModel()
|
||||
self._plugins_model.addRoleName(Qt.UserRole + 1, "name")
|
||||
self._plugins_model.addRoleName(Qt.UserRole + 2, "version")
|
||||
self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description")
|
||||
self._plugins_model.addRoleName(Qt.UserRole + 4, "author")
|
||||
self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed")
|
||||
self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location")
|
||||
self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade")
|
||||
else:
|
||||
self._plugins_model.clear()
|
||||
items = []
|
||||
for metadata in self._plugins_metadata:
|
||||
items.append({
|
||||
"name": metadata["label"],
|
||||
"version": metadata["version"],
|
||||
"short_description": metadata["short_description"],
|
||||
"author": metadata["author"],
|
||||
"already_installed": self._checkAlreadyInstalled(metadata["id"]),
|
||||
"file_location": metadata["file_location"],
|
||||
"can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"])
|
||||
})
|
||||
self._plugins_model.setItems(items)
|
||||
return self._plugins_model
|
||||
|
||||
def _checkCanUpgrade(self, id, version):
|
||||
plugin_registry = PluginRegistry.getInstance()
|
||||
metadata = plugin_registry.getMetaData(id)
|
||||
if metadata != {}:
|
||||
if id in self._newly_installed_plugin_ids:
|
||||
return False # We already updated this plugin.
|
||||
current_version = Version(metadata["plugin"]["version"])
|
||||
new_version = Version(version)
|
||||
if new_version > current_version:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _checkAlreadyInstalled(self, id):
|
||||
plugin_registry = PluginRegistry.getInstance()
|
||||
metadata = plugin_registry.getMetaData(id)
|
||||
if metadata != {}:
|
||||
return True
|
||||
else:
|
||||
if id in self._newly_installed_plugin_ids:
|
||||
return True # We already installed this plugin, but the registry just doesn't know it yet.
|
||||
return False
|
||||
|
||||
def _onRequestFinished(self, reply):
|
||||
reply_url = reply.url().toString()
|
||||
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||
if reply_url == self._api_url + "plugins":
|
||||
try:
|
||||
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
self._plugins_metadata = json_data
|
||||
self.pluginsMetadataChanged.emit()
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
|
||||
return
|
||||
else:
|
||||
# Ignore any operation that is not a get operation
|
||||
pass
|
||||
|
||||
def _createNetworkManager(self):
|
||||
if self._network_manager:
|
||||
self._network_manager.finished.disconnect(self._onRequestFinished)
|
||||
|
||||
self._network_manager = QNetworkAccessManager()
|
||||
self._network_manager.finished.connect(self._onRequestFinished)
|
105
plugins/PluginBrowser/PluginBrowser.qml
Normal file
105
plugins/PluginBrowser/PluginBrowser.qml
Normal file
@ -0,0 +1,105 @@
|
||||
import UM 1.1 as UM
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: base
|
||||
|
||||
title: catalog.i18nc("@title:window", "Find & Update plugins")
|
||||
width: 600
|
||||
height: 450
|
||||
Item
|
||||
{
|
||||
anchors.fill: parent
|
||||
Label
|
||||
{
|
||||
id: introText
|
||||
text: catalog.i18nc("@label", "Here you can find a list of Third Party plugins.")
|
||||
width: parent.width
|
||||
height: 30
|
||||
}
|
||||
ScrollView
|
||||
{
|
||||
width: parent.width
|
||||
anchors.top: introText.bottom
|
||||
anchors.bottom: progressbar.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
frameVisible: true
|
||||
ListView
|
||||
{
|
||||
id: pluginList
|
||||
model: manager.pluginsModel
|
||||
anchors.fill: parent
|
||||
|
||||
delegate: pluginDelegate
|
||||
}
|
||||
}
|
||||
ProgressBar
|
||||
{
|
||||
id: progressbar
|
||||
anchors.bottom: parent.bottom
|
||||
style: UM.Theme.styles.progressbar
|
||||
minimumValue: 0;
|
||||
maximumValue: 100
|
||||
width: parent.width
|
||||
height: 10
|
||||
value: manager.downloadProgress
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
SystemPalette { id: palette }
|
||||
Component
|
||||
{
|
||||
id: pluginDelegate
|
||||
Rectangle
|
||||
{
|
||||
width: pluginList.width;
|
||||
height: childrenRect.height;
|
||||
color: index % 2 ? palette.base : palette.alternateBase
|
||||
Column
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: downloadButton.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
Label
|
||||
{
|
||||
text: "<b>" + model.name + "</b> - " + model.author
|
||||
width: contentWidth
|
||||
height: contentHeight + UM.Theme.getSize("default_margin").height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: model.short_description
|
||||
width: parent.width
|
||||
height: contentHeight + UM.Theme.getSize("default_margin").height
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
Button
|
||||
{
|
||||
id: downloadButton
|
||||
text: !model.already_installed ? catalog.i18nc("@action:button", "Download") : model.can_upgrade ? catalog.i18nc("@action:button", "Upgrade") : catalog.i18nc("@action:button", "Download")
|
||||
onClicked: manager.downloadAndInstallPlugin(model.file_location)
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
enabled: (!model.already_installed || model.can_upgrade) && !manager.isDownloading
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
UM.I18nCatalog { id: catalog; name:"cura" }
|
||||
}
|
||||
}
|
12
plugins/PluginBrowser/__init__.py
Normal file
12
plugins/PluginBrowser/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# PluginBrowser is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from . import PluginBrowser
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
|
||||
def register(app):
|
||||
return {"extension": PluginBrowser.PluginBrowser()}
|
7
plugins/PluginBrowser/plugin.json
Normal file
7
plugins/PluginBrowser/plugin.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Plugin Browser",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"api": 4,
|
||||
"description": "Find, manage and install new plugins."
|
||||
}
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Removable Drive Output Device Plugin"),
|
||||
"author": "Ultimaker B.V.",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides removable drive hotplugging and writing support."),
|
||||
"version": "1.0",
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
|
8
plugins/RemovableDriveOutputDevice/plugin.json
Normal file
8
plugins/RemovableDriveOutputDevice/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Removable Drive Output Device Plugin",
|
||||
"author": "Ultimaker",
|
||||
"description": "Provides removable drive hotplugging and writing support.",
|
||||
"version": "1.0.0",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -6,13 +6,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Slice info"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Submits anonymous slice info. Can be disabled through preferences."),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
|
8
plugins/SliceInfoPlugin/plugin.json
Normal file
8
plugins/SliceInfoPlugin/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Slice info",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -7,7 +7,7 @@ from UM.Scene.Selection import Selection
|
||||
from UM.Resources import Resources
|
||||
from UM.Application import Application
|
||||
from UM.Preferences import Preferences
|
||||
from UM.View.Renderer import Renderer
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.Settings.Validator import ValidatorState
|
||||
from UM.Math.Color import Color
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
@ -118,7 +118,7 @@ class SolidView(View):
|
||||
else:
|
||||
renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms)
|
||||
if node.callDecoration("isGroup") and Selection.isSelected(node):
|
||||
renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = Renderer.RenderLines)
|
||||
renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = RenderBatch.RenderMode.LineLoop)
|
||||
|
||||
def endRendering(self):
|
||||
pass
|
||||
|
@ -8,13 +8,6 @@ i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": i18n_catalog.i18nc("@label", "Solid View"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides a normal solid mesh view."),
|
||||
"api": 3
|
||||
},
|
||||
"view": {
|
||||
"name": i18n_catalog.i18nc("@item:inmenu", "Solid"),
|
||||
"weight": 0
|
||||
|
8
plugins/SolidView/plugin.json
Normal file
8
plugins/SolidView/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Solid View",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a normal solid mesh view.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -12,7 +12,6 @@ Cura.MachineAction
|
||||
anchors.fill: parent;
|
||||
property var selectedPrinter: null
|
||||
property bool completeProperties: true
|
||||
property var connectingToPrinter: null
|
||||
|
||||
Connections
|
||||
{
|
||||
@ -33,9 +32,8 @@ Cura.MachineAction
|
||||
if(base.selectedPrinter && base.completeProperties)
|
||||
{
|
||||
var printerKey = base.selectedPrinter.getKey()
|
||||
if(connectingToPrinter != printerKey) {
|
||||
// prevent an infinite loop
|
||||
connectingToPrinter = printerKey;
|
||||
if(manager.getStoredKey() != printerKey)
|
||||
{
|
||||
manager.setKey(printerKey);
|
||||
completed();
|
||||
}
|
||||
|
34
plugins/UM3NetworkPrinting/MonitorItem.qml
Normal file
34
plugins/UM3NetworkPrinting/MonitorItem.qml
Normal file
@ -0,0 +1,34 @@
|
||||
import QtQuick 2.2
|
||||
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Component
|
||||
{
|
||||
Image
|
||||
{
|
||||
id: cameraImage
|
||||
width: sourceSize.width
|
||||
height: sourceSize.height * width / sourceSize.width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onVisibleChanged:
|
||||
{
|
||||
if(visible)
|
||||
{
|
||||
OutputDevice.startCamera()
|
||||
} else
|
||||
{
|
||||
OutputDevice.stopCamera()
|
||||
}
|
||||
}
|
||||
source:
|
||||
{
|
||||
if(OutputDevice.cameraImage)
|
||||
{
|
||||
return OutputDevice.cameraImage;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -178,6 +178,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||
self._last_command = ""
|
||||
|
||||
self._compressing_print = False
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
|
||||
|
||||
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
|
||||
if printer_type.startswith("9511"):
|
||||
@ -306,8 +307,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||
def _stopCamera(self):
|
||||
self._camera_timer.stop()
|
||||
if self._image_reply:
|
||||
self._image_reply.abort()
|
||||
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
|
||||
try:
|
||||
self._image_reply.abort()
|
||||
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
|
||||
except RuntimeError:
|
||||
pass # It can happen that the wrapped c++ object is already deleted.
|
||||
self._image_reply = None
|
||||
self._image_request = None
|
||||
|
||||
|
@ -6,15 +6,7 @@ from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": "UM3 Network Connection",
|
||||
"author": "Ultimaker",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Manages network connections to Ultimaker 3 printers"),
|
||||
"version": "1.0",
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
|
8
plugins/UM3NetworkPrinting/plugin.json
Normal file
8
plugins/UM3NetworkPrinting/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "UM3 Network Connection",
|
||||
"author": "Ultimaker",
|
||||
"description": "Manages network connections to Ultimaker 3 printers",
|
||||
"version": "1.0.0",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -8,14 +8,6 @@ i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"type": "extension",
|
||||
"plugin": {
|
||||
"name": i18n_catalog.i18nc("@label", "USB printing"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"api": 3,
|
||||
"description": i18n_catalog.i18nc("@info:whatsthis","Accepts G-Code and sends them to a printer. Plugin can also update firmware.")
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
|
8
plugins/USBPrinting/plugin.json
Normal file
8
plugins/USBPrinting/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "USB printing",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"api": 4,
|
||||
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -52,7 +52,6 @@ class UMOUpgradeSelection(MachineAction):
|
||||
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||
|
||||
UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().addContainer(definition_changes_container)
|
||||
# Insert definition_changes between the definition and the variant
|
||||
global_container_stack.insertContainer(-1, definition_changes_container)
|
||||
global_container_stack.definitionChanges = definition_changes_container
|
||||
|
||||
return definition_changes_container
|
||||
|
@ -12,13 +12,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Ultimaker machine actions"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)"),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
|
8
plugins/UltimakerMachineActions/plugin.json
Normal file
8
plugins/UltimakerMachineActions/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Ultimaker machine actions",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc)",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -10,13 +10,6 @@ upgrade = VersionUpgrade21to22.VersionUpgrade21to22()
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Version Upgrade 2.1 to 2.2"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.1 to Cura 2.2."),
|
||||
"api": 3
|
||||
},
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("profile", 1000000): ("quality", 2000000, upgrade.upgradeProfile),
|
||||
|
8
plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade21to22/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Version Upgrade 2.1 to 2.2",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -10,13 +10,6 @@ upgrade = VersionUpgrade.VersionUpgrade22to24()
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Version Upgrade 2.2 to 2.4"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.2 to Cura 2.4."),
|
||||
"api": 3
|
||||
},
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("machine_instance", 2000000): ("machine_stack", 3000000, upgrade.upgradeMachineInstance),
|
||||
|
8
plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade22to24/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Version Upgrade 2.2 to 2.4",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -10,21 +10,15 @@ upgrade = VersionUpgrade25to26.VersionUpgrade25to26()
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Version Upgrade 2.5 to 2.6"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.5 to Cura 2.6."),
|
||||
"api": 3
|
||||
},
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("preferences", 4000000): ("preferences", 4000001, upgrade.upgradePreferences),
|
||||
# NOTE: All the instance containers share the same general/version, so we have to update all of them
|
||||
# if any is updated.
|
||||
("quality_changes", 2000000): ("quality_changes", 2000001, upgrade.upgradeInstanceContainer),
|
||||
("user", 2000000): ("user", 2000001, upgrade.upgradeInstanceContainer),
|
||||
("quality", 2000000): ("quality", 2000001, upgrade.upgradeInstanceContainer),
|
||||
("quality_changes", 2000000): ("quality_changes", 2000001, upgrade.upgradeInstanceContainer),
|
||||
("user", 2000000): ("user", 2000001, upgrade.upgradeInstanceContainer),
|
||||
("quality", 2000000): ("quality", 2000001, upgrade.upgradeInstanceContainer),
|
||||
("definition_changes", 2000000): ("definition_changes", 2000001, upgrade.upgradeInstanceContainer),
|
||||
},
|
||||
"sources": {
|
||||
"quality_changes": {
|
||||
@ -39,6 +33,10 @@ def getMetaData():
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./user"}
|
||||
},
|
||||
"definition_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./machine_instances"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
8
plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade25to26/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Version Upgrade 2.5 to 2.6",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -7,13 +7,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "X3D Reader"),
|
||||
"author": "Seva Alekseyev",
|
||||
"version": "0.5",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides support for reading X3D files."),
|
||||
"api": 3
|
||||
},
|
||||
"mesh_reader": [
|
||||
{
|
||||
"extension": "x3d",
|
||||
|
8
plugins/X3DReader/plugin.json
Normal file
8
plugins/X3DReader/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "X3D Reader",
|
||||
"author": "Seva Alekseyev",
|
||||
"version": "0.5.0",
|
||||
"description": "Provides support for reading X3D files.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
@ -8,13 +8,6 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "X-Ray View"),
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides the X-Ray view."),
|
||||
"api": 3
|
||||
},
|
||||
"view": {
|
||||
"name": catalog.i18nc("@item:inlistbox", "X-Ray"),
|
||||
"weight": 1
|
||||
|
8
plugins/XRayView/plugin.json
Normal file
8
plugins/XRayView/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "X-Ray View",
|
||||
"author": "Ultimaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the X-Ray view.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
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