Merge pull request #6 from Ultimaker/master

Update
This commit is contained in:
MaukCC 2017-06-28 10:11:44 +02:00 committed by GitHub
commit 8313246c1c
240 changed files with 26163 additions and 7734 deletions

3
.gitignore vendored
View File

@ -30,9 +30,6 @@ cura.desktop
.pydevproject
.settings
# Debian packaging
debian*
#Externally located plug-ins.
plugins/Doodle3D-cura-plugin
plugins/GodMode

9
Jenkinsfile vendored
View File

@ -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".

View File

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

View File

@ -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)]

View File

@ -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):

View File

@ -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)

View File

@ -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():

View File

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

View File

@ -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.

View File

@ -23,8 +23,8 @@ class PlatformPhysicsOperation(Operation):
def mergeWith(self, other):
group = GroupedOperation()
group.addOperation(self)
group.addOperation(other)
group.addOperation(self)
return group

View File

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

View File

@ -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.

View File

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

View File

@ -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):

View File

@ -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]

View File

@ -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,

View File

@ -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.

View File

@ -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)

View File

@ -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()

View File

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

View File

@ -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"):

View 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))

View 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()

View File

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

View File

@ -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)

View File

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

View File

@ -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):

View File

@ -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()

View File

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

View File

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

View File

@ -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"] = [
{

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

View File

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

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

View File

@ -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() }

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

View File

@ -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 wont 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
Weve 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
Weve improved placing multiple models or multiplying the same ones, making it easier to arrange your build plate. If theres 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
Its 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
Weve 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
Its a lot simpler to save and open files, and Cura will know if its 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
Weve 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
Weve 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.

View File

@ -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()}

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

View File

@ -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:

View File

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

View File

@ -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() }

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

View File

@ -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",

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

View File

@ -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",

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

View File

@ -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",

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

View File

@ -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",

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

View File

@ -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:

View File

@ -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": [{

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

View File

@ -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",

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

View File

@ -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))

View File

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

View File

@ -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",

View File

@ -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));

View File

@ -0,0 +1,8 @@
{
"name": "Layer View",
"author": "Ultimaker",
"version": "1.0.0",
"description": "Provides the Layer view.",
"api": 4,
"i18n-catalog": "cura"
}

View File

@ -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",

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

View File

@ -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):

View File

@ -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() }

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

View File

@ -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;

View File

@ -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"),

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

View 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)

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

View 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()}

View File

@ -0,0 +1,7 @@
{
"name": "Plugin Browser",
"author": "Ultimaker",
"version": "1.0.0",
"api": 4,
"description": "Find, manage and install new plugins."
}

View File

@ -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):

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

View File

@ -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):

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

View File

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

View File

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

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

View File

@ -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();
}

View 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 "";
}
}
}

View File

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

View File

@ -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()}

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

View File

@ -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):

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

View File

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

View File

@ -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):

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

View File

@ -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),

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

View File

@ -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),

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

View File

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

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

View File

@ -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",

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

View File

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

View 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