CURA-5402 hopefully solved merge conflicts

This commit is contained in:
Jack Ha 2018-06-06 14:39:53 +02:00
commit 96f2e6e1ed
96 changed files with 1612 additions and 838 deletions

View file

@ -19,7 +19,10 @@ endif()
set(CURA_VERSION "master" CACHE STRING "Version name of Cura")
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
set(CURA_PACKAGES_VERSION "" CACHE STRING "Packages version of Cura")
set(CURA_SDK_VERSION "" CACHE STRING "SDK version of Cura")
set(CURA_CLOUD_API_ROOT "" CACHE STRING "Alternative Cura cloud API root")
set(CURA_CLOUD_API_VERSION "" CACHE STRING "Alternative Cura cloud API version")
configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)

View file

@ -3,6 +3,7 @@
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
from UM.Math.Polygon import Polygon
from UM.Math.Vector import Vector
from cura.Arranging.ShapeArray import ShapeArray
from cura.Scene import ZOffsetDecorator
@ -45,7 +46,7 @@ class Arrange:
# \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed
@classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250):
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8):
arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst()
@ -58,9 +59,10 @@ class Arrange:
# Place all objects fixed nodes
for fixed_node in fixed_nodes:
vertices = fixed_node.callDecoration("getConvexHull")
vertices = fixed_node.callDecoration("getConvexHullHead") or fixed_node.callDecoration("getConvexHull")
if not vertices:
continue
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
points = copy.deepcopy(vertices._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr)
@ -81,12 +83,12 @@ class Arrange:
## Find placement for a node (using offset shape) and place it (using hull shape)
# return the nodes that should be placed
# \param node
# \param offset_shape_arr ShapeArray with offset, used to find location
# \param hull_shape_arr ShapeArray without offset, for placing the shape
# \param offset_shape_arr ShapeArray with offset, for placing the shape
# \param hull_shape_arr ShapeArray without offset, used to find location
def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
new_node = copy.deepcopy(node)
best_spot = self.bestSpot(
offset_shape_arr, start_prio = self._last_priority, step = step)
hull_shape_arr, start_prio = self._last_priority, step = step)
x, y = best_spot.x, best_spot.y
# Save the last priority.
@ -102,7 +104,7 @@ class Arrange:
if x is not None: # We could find a place
new_node.setPosition(Vector(x, center_y, y))
found_spot = True
self.place(x, y, hull_shape_arr) # place the object in arranger
self.place(x, y, offset_shape_arr) # place the object in arranger
else:
Logger.log("d", "Could not find spot!"),
found_spot = False

View file

@ -110,7 +110,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
arrange_array.add()
arranger = arrange_array.get(current_build_plate_number)
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority)
best_spot = arranger.bestSpot(hull_shape_arr, start_prio=start_priority)
x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox():
@ -118,7 +118,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
else:
center_y = 0
if x is not None: # We could find a place
arranger.place(x, y, hull_shape_arr) # place the object in the arranger
arranger.place(x, y, offset_shape_arr) # place the object in the arranger
node.callDecoration("setBuildPlateNumber", current_build_plate_number)
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))

View file

@ -37,12 +37,15 @@ class ArrangeObjectsJob(Job):
machine_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "value")
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes)
arranger = Arrange.create(x = machine_width, y = machine_depth, fixed_nodes = self._fixed_nodes, min_offset = self._min_offset)
# Collect nodes to be placed
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
for node in self._nodes:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
if offset_shape_arr is None:
Logger.log("w", "Node [%s] could not be converted to an array for arranging...", str(node))
continue
nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr))
# Sort the nodes with the biggest area first.
@ -63,7 +66,7 @@ class ArrangeObjectsJob(Job):
start_priority = last_priority
else:
start_priority = 0
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority)
best_spot = arranger.bestSpot(hull_shape_arr, start_prio = start_priority)
x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox():
@ -74,7 +77,7 @@ class ArrangeObjectsJob(Job):
last_size = size
last_priority = best_spot.priority
arranger.place(x, y, hull_shape_arr) # take place before the next one
arranger.place(x, y, offset_shape_arr) # take place before the next one
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
else:
Logger.log("d", "Arrange all: could not find spot!")

View file

@ -3,21 +3,20 @@
from PyQt5.QtCore import QTimer
from UM.Preferences import Preferences
from UM.Logger import Logger
class AutoSave:
def __init__(self, application):
self._application = application
Preferences.getInstance().preferenceChanged.connect(self._triggerTimer)
self._application.getPreferences().preferenceChanged.connect(self._triggerTimer)
self._global_stack = None
Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10)
self._application.getPreferences().addPreference("cura/autosave_delay", 1000 * 10)
self._change_timer = QTimer()
self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay"))
self._change_timer.setInterval(self._application.getPreferences().getValue("cura/autosave_delay"))
self._change_timer.setSingleShot(True)
self._saving = False

View file

@ -3,12 +3,10 @@
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from UM.Scene.Platform import Platform
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Application import Application
from UM.Resources import Resources
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Math.Vector import Vector
@ -37,8 +35,10 @@ PRIME_CLEARANCE = 6.5
class BuildVolume(SceneNode):
raftThicknessChanged = Signal()
def __init__(self, parent = None):
def __init__(self, application, parent = None):
super().__init__(parent)
self._application = application
self._machine_manager = self._application.getMachineManager()
self._volume_outline_color = None
self._x_axis_color = None
@ -82,14 +82,14 @@ class BuildVolume(SceneNode):
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged)
self._application.globalContainerStackChanged.connect(self._onStackChanged)
self._onStackChanged()
self._engine_ready = False
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
self._application.engineCreatedSignal.connect(self._onEngineCreated)
self._has_errors = False
Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
#Objects loaded at the moment. We are connected to the property changed events of these objects.
self._scene_objects = set()
@ -107,14 +107,14 @@ class BuildVolume(SceneNode):
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
# Therefore this works.
Application.getInstance().getMachineManager().activeQualityChanged.connect(self._onStackChanged)
self._machine_manager.activeQualityChanged.connect(self._onStackChanged)
# This should also ways work, and it is semantically more correct,
# but it does not update the disallowed areas after material change
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
self._machine_manager.activeStackChanged.connect(self._onStackChanged)
# Enable and disable extruder
Application.getInstance().getMachineManager().extruderChanged.connect(self.updateNodeBoundaryCheck)
self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck)
# list of settings which were updated
self._changed_settings_since_last_rebuild = []
@ -124,7 +124,7 @@ class BuildVolume(SceneNode):
self._scene_change_timer.start()
def _onSceneChangeTimerFinished(self):
root = Application.getInstance().getController().getScene().getRoot()
root = self._application.getController().getScene().getRoot()
new_scene_objects = set(node for node in BreadthFirstIterator(root) if node.callDecoration("isSliceable"))
if new_scene_objects != self._scene_objects:
for node in new_scene_objects - self._scene_objects: #Nodes that were added to the scene.
@ -186,7 +186,7 @@ class BuildVolume(SceneNode):
if not self._shader:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader"))
self._grid_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "grid.shader"))
theme = Application.getInstance().getTheme()
theme = self._application.getTheme()
self._grid_shader.setUniformValue("u_plateColor", Color(*theme.getColor("buildplate").getRgb()))
self._grid_shader.setUniformValue("u_gridColor0", Color(*theme.getColor("buildplate_grid").getRgb()))
self._grid_shader.setUniformValue("u_gridColor1", Color(*theme.getColor("buildplate_grid_minor").getRgb()))
@ -206,7 +206,7 @@ class BuildVolume(SceneNode):
## For every sliceable node, update node._outside_buildarea
#
def updateNodeBoundaryCheck(self):
root = Application.getInstance().getController().getScene().getRoot()
root = self._application.getController().getScene().getRoot()
nodes = list(BreadthFirstIterator(root))
group_nodes = []
@ -294,11 +294,11 @@ class BuildVolume(SceneNode):
if not self._width or not self._height or not self._depth:
return
if not Application.getInstance()._engine:
if not self._application._qml_engine:
return
if not self._volume_outline_color:
theme = Application.getInstance().getTheme()
theme = self._application.getTheme()
self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
@ -470,7 +470,7 @@ class BuildVolume(SceneNode):
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
)
Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds
self.updateNodeBoundaryCheck()
@ -523,7 +523,7 @@ class BuildVolume(SceneNode):
for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingPropertyChanged)
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
self._global_container_stack = self._application.getGlobalContainerStack()
if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
@ -566,7 +566,7 @@ class BuildVolume(SceneNode):
if setting_key == "print_sequence":
machine_height = self._global_container_stack.getProperty("machine_height", "value")
if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1:
if self._application.getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1:
self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height)
if self._height < machine_height:
self._build_volume_message.show()

View file

@ -12,7 +12,6 @@ from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation
from cura.Operations.SetParentOperation import SetParentOperation

View file

@ -1,9 +1,18 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, QTimer
from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket
import copy
import json
import os
import sys
import time
import numpy
from PyQt5.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from UM.Qt.QtApplication import QtApplication
from UM.Scene.SceneNode import SceneNode
@ -74,6 +83,7 @@ from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager
from .SingleInstance import SingleInstance
from .AutoSave import AutoSave
from . import PlatformPhysics
from . import BuildVolume
@ -94,22 +104,10 @@ from cura.Settings.ContainerManager import ContainerManager
from cura.ObjectsModel import ObjectsModel
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
import sys
import numpy
import copy
import os
import argparse
import json
import time
numpy.seterr(all="ignore")
numpy.seterr(all = "ignore")
MYPY = False
if not MYPY:
@ -144,20 +142,161 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes)
def __init__(self, **kwargs):
def __init__(self, *args, **kwargs):
super().__init__(name = "cura",
version = CuraVersion,
buildtype = CuraBuildType,
is_debug_mode = CuraDebugMode,
tray_icon_name = "cura-icon-32.png",
**kwargs)
self.default_theme = "cura-light"
self._boot_loading_time = time.time()
# Variables set from CLI
self._files_to_open = []
self._use_single_instance = False
self._trigger_early_crash = False # For debug only
self._single_instance = None
self._cura_package_manager = None
self._machine_action_manager = None
self.empty_container = None
self.empty_definition_changes_container = None
self.empty_variant_container = None
self.empty_material_container = None
self.empty_quality_container = None
self.empty_quality_changes_container = None
self._variant_manager = None
self._material_manager = None
self._quality_manager = None
self._machine_manager = None
self._extruder_manager = None
self._container_manager = None
self._object_manager = None
self._build_plate_model = None
self._multi_build_plate_model = None
self._setting_visibility_presets_model = None
self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None
self._cura_scene_controller = None
self._machine_error_checker = None
self._quality_profile_drop_down_menu_model = None
self._custom_quality_profile_drop_down_menu_model = None
self._physics = None
self._volume = None
self._output_devices = {}
self._print_information = None
self._previous_active_tool = None
self._platform_activity = False
self._scene_bounding_box = AxisAlignedBox.Null
self._center_after_select = False
self._camera_animation = None
self._cura_actions = None
self.started = False
self._message_box_callback = None
self._message_box_callback_arguments = []
self._preferred_mimetype = ""
self._i18n_catalog = None
self._currently_loading_files = []
self._non_sliceable_extensions = []
self._additional_components = {} # Components to add to certain areas in the interface
self._open_file_queue = [] # A list of files to open (after the application has started)
self._update_platform_activity_timer = None
self._need_to_show_user_agreement = True
# Backups
self._auto_save = None
self._save_data_enabled = True
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
self._container_registry_class = CuraContainerRegistry
# Adds command line options to the command line parser. This should be called after the application is created and
# before the pre-start.
def addCommandLineOptions(self):
super().addCommandLineOptions()
self._cli_parser.add_argument("--help", "-h",
action = "store_true",
default = False,
help = "Show this help message and exit.")
self._cli_parser.add_argument("--single-instance",
dest = "single_instance",
action = "store_true",
default = False)
# >> For debugging
# Trigger an early crash, i.e. a crash that happens before the application enters its event loop.
self._cli_parser.add_argument("--trigger-early-crash",
dest = "trigger_early_crash",
action = "store_true",
default = False,
help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog.")
self._cli_parser.add_argument("file", nargs = "*", help = "Files to load after starting the application.")
def parseCliOptions(self):
super().parseCliOptions()
if self._cli_args.help:
self._cli_parser.print_help()
sys.exit(0)
self._use_single_instance = self._cli_args.single_instance
self._trigger_early_crash = self._cli_args.trigger_early_crash
for filename in self._cli_args.file:
self._files_to_open.append(os.path.abspath(filename))
def initialize(self) -> None:
self.__addExpectedResourceDirsAndSearchPaths() # Must be added before init of super
super().initialize()
self.__sendCommandToSingleInstance()
self.__initializeSettingDefinitionsAndFunctions()
self.__addAllResourcesAndContainerResources()
self.__addAllEmptyContainers()
self.__setLatestResouceVersionsForVersionUpgrade()
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
self._machine_action_manager.initialize()
def __sendCommandToSingleInstance(self):
self._single_instance = SingleInstance(self, self._files_to_open)
# If we use single instance, try to connect to the single instance server, send commands, and then exit.
# If we cannot find an existing single instance server, this is the only instance, so just keep going.
if self._use_single_instance:
if self._single_instance.startClient():
Logger.log("i", "Single instance commands were sent, exiting")
sys.exit(0)
# Adds expected directory names and search paths for Resources.
def __addExpectedResourceDirsAndSearchPaths(self):
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants"]:
Resources.addExpectedDirNameInData(dir_name)
Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
Resources.addSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
if not hasattr(sys, "frozen"):
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
Resources.addSearchPath(resource_path)
self._use_gui = True
self._open_file_queue = [] # Files to open when plug-ins are loaded.
# Adds custom property types, settings types, and extra operators (functions) that need to be registered in
# SettingDefinition and SettingFunction.
def __initializeSettingDefinitionsAndFunctions(self):
# Need to do this before ContainerRegistry tries to load the machines
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
@ -181,8 +320,10 @@ class CuraApplication(QtApplication):
SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues)
SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue)
SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue)
SettingFunction.registerOperator("defaultExtruderPosition", ExtruderManager.getDefaultExtruderPosition)
## Add the 4 types of profiles to storage.
# Adds all resources and container related resources.
def __addAllResourcesAndContainerResources(self) -> None:
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants")
@ -193,20 +334,64 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack, "machine")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
self._container_registry.addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant")
self._container_registry.addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material")
self._container_registry.addResourceType(self.ResourceTypes.UserInstanceContainer, "user")
self._container_registry.addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train")
self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine")
self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
## Initialise the version upgrade manager with Cura's storage paths.
# Needs to be here to prevent circular dependencies.
import UM.VersionUpgradeManager
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware")
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
# Adds all empty containers.
def __addAllEmptyContainers(self) -> None:
# Add empty variant, material and quality containers.
# Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials.
empty_container = self._container_registry.getEmptyInstanceContainer()
self.empty_container = empty_container
empty_definition_changes_container = copy.deepcopy(empty_container)
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
empty_definition_changes_container.addMetaDataEntry("type", "definition_changes")
self._container_registry.addContainer(empty_definition_changes_container)
self.empty_definition_changes_container = empty_definition_changes_container
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant")
self._container_registry.addContainer(empty_variant_container)
self.empty_variant_container = empty_variant_container
empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material")
self._container_registry.addContainer(empty_material_container)
self.empty_material_container = empty_material_container
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
self._container_registry.addContainer(empty_quality_container)
self.empty_quality_container = empty_quality_container
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
self._container_registry.addContainer(empty_quality_changes_container)
self.empty_quality_changes_container = empty_quality_changes_container
# Initializes the version upgrade manager with by providing the paths for each resource type and the latest
# versions.
def __setLatestResouceVersionsForVersionUpgrade(self):
self._version_upgrade_manager.setCurrentVersions(
{
("quality", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityChangesInstanceContainer, "application/x-uranium-instancecontainer"),
@ -219,6 +404,7 @@ class CuraApplication(QtApplication):
}
)
"""
self._currently_loading_files = []
self._non_sliceable_extensions = []
@ -254,6 +440,10 @@ class CuraApplication(QtApplication):
self._variant_manager = None
self.default_theme = "cura-light"
"""
# Runs preparations that needs to be done before the starting process.
def startSplashWindowPhase(self):
super().startSplashWindowPhase()
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@ -287,23 +477,6 @@ class CuraApplication(QtApplication):
"SelectionTool",
"TranslateTool",
])
self._physics = None
self._volume = None
self._output_devices = {}
self._print_information = None
self._previous_active_tool = None
self._platform_activity = False
self._scene_bounding_box = AxisAlignedBox.Null
self._job_name = None
self._center_after_select = False
self._camera_animation = None
self._cura_actions = None
self.started = False
self._message_box_callback = None
self._message_box_callback_arguments = []
self._preferred_mimetype = ""
self._i18n_catalog = i18nCatalog("cura")
self._update_platform_activity_timer = QTimer()
@ -316,56 +489,13 @@ class CuraApplication(QtApplication):
self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed)
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware")
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))
# Add empty variant, material and quality containers.
# Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials.
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self.empty_container = empty_container
empty_definition_changes_container = copy.deepcopy(empty_container)
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
empty_definition_changes_container.addMetaDataEntry("type", "definition_changes")
ContainerRegistry.getInstance().addContainer(empty_definition_changes_container)
self.empty_definition_changes_container = empty_definition_changes_container
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container)
self.empty_variant_container = empty_variant_container
empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container)
self.empty_material_container = empty_material_container
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container)
self.empty_quality_container = empty_quality_container
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
self.empty_quality_changes_container = empty_quality_changes_container
with ContainerRegistry.getInstance().lockFile():
ContainerRegistry.getInstance().loadAllMetadata()
with self._container_registry.lockFile():
self._container_registry.loadAllMetadata()
# set the setting version for Preferences
preferences = Preferences.getInstance()
preferences = self.getPreferences()
preferences.addPreference("metadata/setting_version", 0)
preferences.setValue("metadata/setting_version", self.SettingVersion) #Don't make it equal to the default so that the setting version always gets written to the file.
@ -391,7 +521,7 @@ class CuraApplication(QtApplication):
preferences.addPreference("view/filter_current_build_plate", False)
preferences.addPreference("cura/sidebar_collapsed", False)
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
self._need_to_show_user_agreement = not self.getPreferences().getValue("general/accepted_user_agreement")
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
@ -410,13 +540,10 @@ class CuraApplication(QtApplication):
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
self._quality_profile_drop_down_menu_model = None
self._custom_quality_profile_drop_down_menu_model = None
CuraApplication.Created = True
def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@pyqtProperty(bool)
def needToShowUserAgreement(self):
@ -445,7 +572,7 @@ class CuraApplication(QtApplication):
## A reusable dialogbox
#
showMessageBox = pyqtSignal(str, str, str, str, str, int, int, arguments = ["title", "footer", "text", "informativeText", "detailedText", "buttons", "icon"])
showMessageBox = pyqtSignal(str, str, str, str, int, int, arguments = ["title", "text", "informativeText", "detailedText", "buttons", "icon"])
def messageBox(self, title, text, informativeText = "", detailedText = "", buttons = QMessageBox.Ok, icon = QMessageBox.NoIcon, callback = None, callback_arguments = []):
self._message_box_callback = callback
@ -456,14 +583,14 @@ class CuraApplication(QtApplication):
def discardOrKeepProfileChanges(self):
has_user_interaction = False
choice = Preferences.getInstance().getValue("cura/choice_on_profile_override")
choice = self.getPreferences().getValue("cura/choice_on_profile_override")
if choice == "always_discard":
# don't show dialog and DISCARD the profile
self.discardOrKeepProfileChangesClosed("discard")
elif choice == "always_keep":
# don't show dialog and KEEP the profile
self.discardOrKeepProfileChangesClosed("keep")
elif self._use_gui:
elif not self._is_headless:
# ALWAYS ask whether to keep or discard the profile
self.showDiscardOrKeepProfileChanges.emit()
has_user_interaction = True
@ -502,24 +629,19 @@ class CuraApplication(QtApplication):
# Do not do saving during application start or when data should not be safed on quit.
return
ContainerRegistry.getInstance().saveDirtyContainers()
Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences,
self._application_name + ".cfg"))
self.savePreferences()
def saveStack(self, stack):
ContainerRegistry.getInstance().saveContainer(stack)
@pyqtSlot(str, result = QUrl)
def getDefaultPath(self, key):
default_path = Preferences.getInstance().getValue("local_file/%s" % key)
default_path = self.getPreferences().getValue("local_file/%s" % key)
return QUrl.fromLocalFile(default_path)
@pyqtSlot(str, str)
def setDefaultPath(self, key, default_path):
Preferences.getInstance().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile())
@classmethod
def getStaticVersion(cls):
return CuraVersion
self.getPreferences().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile())
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistry
@ -545,127 +667,8 @@ class CuraApplication(QtApplication):
self._plugins_loaded = True
@classmethod
def addCommandLineOptions(cls, parser, parsed_command_line = None):
if parsed_command_line is None:
parsed_command_line = {}
super().addCommandLineOptions(parser, parsed_command_line = parsed_command_line)
parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
parser.add_argument("--single-instance", action="store_true", default=False)
# Set up a local socket server which listener which coordinates single instances Curas and accepts commands.
def _setUpSingleInstanceServer(self):
if self.getCommandLineOption("single_instance", False):
self.__single_instance_server = QLocalServer()
self.__single_instance_server.newConnection.connect(self._singleInstanceServerNewConnection)
self.__single_instance_server.listen("ultimaker-cura")
def _singleInstanceServerNewConnection(self):
Logger.log("i", "New connection recevied on our single-instance server")
remote_cura_connection = self.__single_instance_server.nextPendingConnection()
if remote_cura_connection is not None:
def readCommands():
line = remote_cura_connection.readLine()
while len(line) != 0: # There is also a .canReadLine()
try:
payload = json.loads(str(line, encoding="ASCII").strip())
command = payload["command"]
# Command: Remove all models from the build plate.
if command == "clear-all":
self.deleteAll()
# Command: Load a model file
elif command == "open":
self._openFile(payload["filePath"])
# WARNING ^ this method is async and we really should wait until
# the file load is complete before processing more commands.
# Command: Activate the window and bring it to the top.
elif command == "focus":
# Operating systems these days prevent windows from moving around by themselves.
# 'alert' or flashing the icon in the taskbar is the best thing we do now.
self.getMainWindow().alert(0)
# Command: Close the socket connection. We're done.
elif command == "close-connection":
remote_cura_connection.close()
else:
Logger.log("w", "Received an unrecognized command " + str(command))
except json.decoder.JSONDecodeError as ex:
Logger.log("w", "Unable to parse JSON command in _singleInstanceServerNewConnection(): " + repr(ex))
line = remote_cura_connection.readLine()
remote_cura_connection.readyRead.connect(readCommands)
## Perform any checks before creating the main application.
#
# This should be called directly before creating an instance of CuraApplication.
# \returns \type{bool} True if the whole Cura app should continue running.
@classmethod
def preStartUp(cls, parser = None, parsed_command_line = None):
if parsed_command_line is None:
parsed_command_line = {}
# Peek the arguments and look for the 'single-instance' flag.
if not parser:
parser = argparse.ArgumentParser(prog = "cura", add_help = False) # pylint: disable=bad-whitespace
CuraApplication.addCommandLineOptions(parser, parsed_command_line = parsed_command_line)
# Important: It is important to keep this line here!
# In Uranium we allow to pass unknown arguments to the final executable or script.
parsed_command_line.update(vars(parser.parse_known_args()[0]))
if parsed_command_line["single_instance"]:
Logger.log("i", "Checking for the presence of an ready running Cura instance.")
single_instance_socket = QLocalSocket()
Logger.log("d", "preStartUp(): full server name: " + single_instance_socket.fullServerName())
single_instance_socket.connectToServer("ultimaker-cura")
single_instance_socket.waitForConnected()
if single_instance_socket.state() == QLocalSocket.ConnectedState:
Logger.log("i", "Connection has been made to the single-instance Cura socket.")
# Protocol is one line of JSON terminated with a carriage return.
# "command" field is required and holds the name of the command to execute.
# Other fields depend on the command.
payload = {"command": "clear-all"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII"))
payload = {"command": "focus"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII"))
if len(parsed_command_line["file"]) != 0:
for filename in parsed_command_line["file"]:
payload = {"command": "open", "filePath": filename}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII"))
payload = {"command": "close-connection"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII"))
single_instance_socket.flush()
single_instance_socket.waitForDisconnected()
return False
return True
def preRun(self):
# Last check for unknown commandline arguments
parser = self.getCommandlineParser()
parser.add_argument("--help", "-h",
action='store_true',
default = False,
help = "Show this help message and exit."
)
parsed_args = vars(parser.parse_args()) # This won't allow unknown arguments
if parsed_args["help"]:
parser.print_help()
sys.exit(0)
def run(self):
self.preRun()
container_registry = ContainerRegistry.getInstance()
container_registry = self._container_registry
Logger.log("i", "Initializing variant manager")
self._variant_manager = VariantManager(container_registry)
@ -684,29 +687,34 @@ class CuraApplication(QtApplication):
Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self)
Logger.log("i", "Initializing container manager")
self._container_manager = ContainerManager(self)
Logger.log("i", "Initializing machine error checker")
self._machine_error_checker = MachineErrorChecker(self)
self._machine_error_checker.initialize()
# Check if we should run as single instance or not
self._setUpSingleInstanceServer()
# Check if we should run as single instance or not. If so, set up a local socket server which listener which
# coordinates multiple Cura instances and accepts commands.
if self._use_single_instance:
self.__setUpSingleInstanceServer()
# Setup scene and build volume
root = self.getController().getScene().getRoot()
self._volume = BuildVolume.BuildVolume(self.getController().getScene().getRoot())
self._volume = BuildVolume.BuildVolume(self, root)
Arrange.build_volume = self._volume
# initialize info objects
self._print_information = PrintInformation.PrintInformation()
self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self)
# Initialize setting visibility presets model
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self)
default_visibility_profile = self._setting_visibility_presets_model.getItem(0)
Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
self.getPreferences().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
# Detect in which mode to run and execute that mode
if self.getCommandLineOption("headless", False):
if self._is_headless:
self.runWithoutGUI()
else:
self.runWithGUI()
@ -715,7 +723,6 @@ class CuraApplication(QtApplication):
self.initializationFinished.emit()
Logger.log("d", "Booting Cura took %s seconds", time.time() - self._boot_loading_time)
# For now use a timer to postpone some things that need to be done after the application and GUI are
# initialized, for example opening files because they may show dialogs which can be closed due to incomplete
# GUI initialization.
@ -730,8 +737,12 @@ class CuraApplication(QtApplication):
self.exec_()
def __setUpSingleInstanceServer(self):
if self._use_single_instance:
self._single_instance.startServer()
def _onPostStart(self):
for file_name in self.getCommandLineOption("file", []):
for file_name in self._files_to_open:
self.callLater(self._openFile, file_name)
for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading.
self.callLater(self._openFile, file_name)
@ -740,13 +751,10 @@ class CuraApplication(QtApplication):
## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self):
self._use_gui = False
self.closeSplash()
## Run Cura with GUI (desktop mode).
def runWithGUI(self):
self._use_gui = True
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
controller = self.getController()
@ -796,9 +804,6 @@ class CuraApplication(QtApplication):
# Hide the splash screen
self.closeSplash()
def hasGui(self):
return self._use_gui
@pyqtSlot(result = QObject)
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
return self._setting_visibility_presets_model
@ -813,7 +818,7 @@ class CuraApplication(QtApplication):
def getExtruderManager(self, *args):
if self._extruder_manager is None:
self._extruder_manager = ExtruderManager.createExtruderManager()
self._extruder_manager = ExtruderManager()
return self._extruder_manager
def getVariantManager(self, *args):
@ -936,7 +941,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@ -970,7 +975,7 @@ class CuraApplication(QtApplication):
# Default
self.getController().setActiveTool("TranslateTool")
if Preferences.getInstance().getValue("view/center_on_select"):
if self.getPreferences().getValue("view/center_on_select"):
self._center_after_select = True
else:
if self.getController().getActiveTool():
@ -1317,15 +1322,15 @@ class CuraApplication(QtApplication):
categories = list(set(categories))
categories.sort()
joined = ";".join(categories)
if joined != Preferences.getInstance().getValue("cura/categories_expanded"):
Preferences.getInstance().setValue("cura/categories_expanded", joined)
if joined != self.getPreferences().getValue("cura/categories_expanded"):
self.getPreferences().setValue("cura/categories_expanded", joined)
self.expandedCategoriesChanged.emit()
expandedCategoriesChanged = pyqtSignal()
@pyqtProperty("QStringList", notify = expandedCategoriesChanged)
def expandedCategories(self):
return Preferences.getInstance().getValue("cura/categories_expanded").split(";")
return self.getPreferences().getValue("cura/categories_expanded").split(";")
@pyqtSlot()
def mergeSelected(self):
@ -1476,8 +1481,7 @@ class CuraApplication(QtApplication):
# see GroupDecorator._onChildrenChanged
def _createSplashScreen(self):
run_headless = self.getCommandLineOption("headless", False)
if run_headless:
if self._is_headless:
return None
return CuraSplashScreen.CuraSplashScreen()
@ -1489,11 +1493,15 @@ class CuraApplication(QtApplication):
def _reloadMeshFinished(self, job):
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
mesh_data = job.getResult()[0].getMeshData()
if mesh_data:
job._node.setMeshData(mesh_data)
else:
job_result = job.getResult()
if len(job_result) == 0:
Logger.log("e", "Reloading the mesh failed.")
return
mesh_data = job_result[0].getMeshData()
if not mesh_data:
Logger.log("w", "Could not find a mesh in reloaded node.")
return
job._node.setMeshData(mesh_data)
def _openFile(self, filename):
self.readLocalFile(QUrl.fromLocalFile(filename))
@ -1557,10 +1565,11 @@ class CuraApplication(QtApplication):
f = file.toLocalFile()
extension = os.path.splitext(f)[1]
extension = extension.lower()
filename = os.path.basename(f)
if len(self._currently_loading_files) > 0:
# If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files
if extension.lower() in self._non_sliceable_extensions:
if extension in self._non_sliceable_extensions:
message = Message(
self._i18n_catalog.i18nc("@info:status",
"Only one G-code file can be loaded at a time. Skipped importing {0}",
@ -1569,7 +1578,8 @@ class CuraApplication(QtApplication):
return
# If file being loaded is non-slicable file, then prevent loading of any other files
extension = os.path.splitext(self._currently_loading_files[0])[1]
if extension.lower() in self._non_sliceable_extensions:
extension = extension.lower()
if extension in self._non_sliceable_extensions:
message = Message(
self._i18n_catalog.i18nc("@info:status",
"Can't open any other file if G-code is loading. Skipped importing {0}",
@ -1592,8 +1602,8 @@ class CuraApplication(QtApplication):
self.fileLoaded.emit(filename)
arrange_objects_on_load = (
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load"))
not self.getPreferences().getValue("cura/use_multi_build_plate") or
not self.getPreferences().getValue("cura/not_arrange_objects_on_load"))
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
root = self.getController().getScene().getRoot()

View file

@ -4,4 +4,6 @@
CuraVersion = "@CURA_VERSION@"
CuraBuildType = "@CURA_BUILDTYPE@"
CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
CuraPackagesVersion = "@CURA_PACKAGES_VERSION@"
CuraSDKVersion = "@CURA_SDK_VERSION@"
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"

View file

@ -1,13 +1,13 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.DefinitionContainer import DefinitionContainer
from PyQt5.QtCore import QObject
from UM.FlameProfiler import pyqtSlot
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type
from UM.Settings.DefinitionContainer import DefinitionContainer
## Raised when trying to add an unknown machine action as a required action
class UnknownMachineActionError(Exception):
@ -20,23 +20,27 @@ class NotUniqueMachineActionError(Exception):
class MachineActionManager(QObject):
def __init__(self, parent = None):
def __init__(self, application, parent = None):
super().__init__(parent)
self._application = application
self._machine_actions = {} # Dict of all known machine actions
self._required_actions = {} # Dict of all required actions by definition ID
self._supported_actions = {} # Dict of all supported actions by definition ID
self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID
def initialize(self):
container_registry = self._application.getContainerRegistry()
# Add machine_action as plugin type
PluginRegistry.addType("machine_action", self.addMachineAction)
# Ensure that all containers that were registered before creation of this registry are also handled.
# This should not have any effect, but it makes it safer if we ever refactor the order of things.
for container in ContainerRegistry.getInstance().findDefinitionContainers():
for container in container_registry.findDefinitionContainers():
self._onContainerAdded(container)
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
container_registry.containerAdded.connect(self._onContainerAdded)
def _onContainerAdded(self, container):
## Ensure that the actions are added to this manager

View file

@ -291,9 +291,10 @@ class MaterialManager(QObject):
material_id_metadata_dict = dict()
for node in nodes_to_check:
if node is not None:
# Only exclude the materials that are explicitly specified in the "exclude_materials" field.
# Do not exclude other materials that are of the same type.
for material_id, node in node.material_map.items():
fallback_id = self.getFallbackMaterialIdByMaterialType(node.metadata["material"])
if fallback_id in machine_exclude_materials:
if material_id in machine_exclude_materials:
Logger.log("d", "Exclude material [%s] for machine [%s]",
material_id, machine_definition.getId())
continue

View file

@ -39,6 +39,8 @@ class BaseMaterialsModel(ListModel):
self._extruder_position = 0
self._extruder_stack = None
# Update the stack and the model data when the machine changes
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine
@ -50,9 +52,11 @@ class BaseMaterialsModel(ListModel):
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update)
# Force update the model when the extruder stack changes
self._update()
def setExtruderPosition(self, position: int):
if self._extruder_position != position:
if self._extruder_stack is None or self._extruder_position != position:
self._extruder_position = position
self._updateExtruderStack()
self.extruderPositionChanged.emit()

View file

@ -8,9 +8,9 @@ from configparser import ConfigParser
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
@ -33,7 +33,7 @@ class SettingVisibilityPresetsModel(ListModel):
basic_item = self.items[1]
basic_visibile_settings = ";".join(basic_item["settings"])
self._preferences = Preferences.getInstance()
self._preferences = Application.getInstance().getPreferences()
# Preference to store which preset is currently selected
self._preferences.addPreference("cura/active_setting_visibility_preset", "basic")
# Preference that stores the "custom" set so it can always be restored (even after a restart)

View file

@ -1,6 +1,8 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import copy
from UM.Job import Job
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Message import Message
@ -36,7 +38,7 @@ class MultiplyObjectsJob(Job):
root = scene.getRoot()
scale = 0.5
arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale)
arranger = Arrange.create(x = machine_width, y = machine_depth, scene_root = root, scale = scale, min_offset = self._min_offset)
processed_nodes = []
nodes = []
@ -64,6 +66,8 @@ class MultiplyObjectsJob(Job):
# We do place the nodes one by one, as we want to yield in between.
if not node_too_big:
new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
else:
new_node = copy.deepcopy(node)
if node_too_big or not solution_found:
found_solution_for_all = False
new_location = new_node.getPosition()

View file

@ -8,7 +8,6 @@ from UM.Qt.ListModel import ListModel
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.Preferences import Preferences
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -20,7 +19,7 @@ class ObjectsModel(ListModel):
super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
Preferences.getInstance().preferenceChanged.connect(self._updateDelayed)
Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed)
self._update_timer = QTimer()
self._update_timer.setInterval(100)
@ -38,7 +37,7 @@ class ObjectsModel(ListModel):
def _update(self, *args):
nodes = []
filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")
filter_current_build_plate = Application.getInstance().getPreferences().getValue("view/filter_current_build_plate")
active_build_plate_number = self._build_plate_number
group_nr = 1
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):

View file

@ -8,7 +8,6 @@ from UM.Scene.SceneNode import SceneNode
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Math.Vector import Vector
from UM.Scene.Selection import Selection
from UM.Preferences import Preferences
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
@ -36,8 +35,8 @@ class PlatformPhysics:
self._max_overlap_checks = 10 # How many times should we try to find a new spot per tick?
self._minimum_gap = 2 # It is a minimum distance (in mm) between two models, applicable for small models
Preferences.getInstance().addPreference("physics/automatic_push_free", False)
Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", False)
Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True)
def _onSceneChanged(self, source):
if not source.getMeshData():
@ -71,7 +70,7 @@ class PlatformPhysics:
# Move it downwards if bottom is above platform
move_vector = Vector()
if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
if Application.getInstance().getPreferences().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
move_vector = move_vector.set(y = -bbox.bottom + z_offset)
@ -80,7 +79,7 @@ class PlatformPhysics:
node.addDecorator(ConvexHullDecorator())
# only push away objects if this node is a printing mesh
if not node.callDecoration("isNonPrintingMesh") and Preferences.getInstance().getValue("physics/automatic_push_free"):
if not node.callDecoration("isNonPrintingMesh") and Application.getInstance().getPreferences().getValue("physics/automatic_push_free"):
# Check for collisions between convex hulls
for other_node in BreadthFirstIterator(root):
# Ignore root, ourselves and anything that is not a normal SceneNode.

View file

@ -1,25 +1,25 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Dict
import math
import os.path
import unicodedata
import json
import math
import os
import unicodedata
import re # To create abbreviations for printer names.
from typing import Dict
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
from UM.Application import Application
from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.Qt.Duration import Duration
from UM.Preferences import Preferences
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
from UM.MimeTypeDatabase import MimeTypeDatabase
catalog = i18nCatalog("cura")
## A class for processing and calculating minimum, current and maximum print time as well as managing the job name
#
# This class contains all the logic relating to calculation and slicing for the
@ -48,8 +48,9 @@ class PrintInformation(QObject):
ActiveMachineChanged = 3
Other = 4
def __init__(self, parent = None):
def __init__(self, application, parent = None):
super().__init__(parent)
self._application = application
self.initializeCuraMessagePrintTimeProperties()
@ -60,11 +61,12 @@ class PrintInformation(QObject):
self._pre_sliced = False
self._backend = Application.getInstance().getBackend()
self._backend = self._application.getBackend()
if self._backend:
self._backend.printDurationMessage.connect(self._onPrintDurationMessage)
Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged)
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
self._is_user_specified_job_name = False
self._base_name = ""
self._abbr_machine = ""
self._job_name = ""
@ -72,7 +74,6 @@ class PrintInformation(QObject):
self._active_build_plate = 0
self._initVariablesWithBuildPlate(self._active_build_plate)
self._application = Application.getInstance()
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
self._application.globalContainerStackChanged.connect(self._updateJobName)
@ -81,7 +82,7 @@ class PrintInformation(QObject):
self._application.workspaceLoaded.connect(self.setProjectName)
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
self._onActiveMaterialsChanged()
@ -200,7 +201,7 @@ class PrintInformation(QObject):
self._current_print_time[build_plate_number].setDuration(total_estimated_time)
def _calculateInformation(self, build_plate_number):
global_stack = Application.getInstance().getGlobalContainerStack()
global_stack = self._application.getGlobalContainerStack()
if global_stack is None:
return
@ -209,7 +210,7 @@ class PrintInformation(QObject):
self._material_costs[build_plate_number] = []
self._material_names[build_plate_number] = []
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
material_preference_values = json.loads(self._application.getInstance().getPreferences().getValue("cura/material_settings"))
extruder_stacks = global_stack.extruders
for position, extruder_stack in extruder_stacks.items():
@ -281,10 +282,13 @@ class PrintInformation(QObject):
# Manual override of job name should also set the base name so that when the printer prefix is updated, it the
# prefix can be added to the manually added name, not the old base name
@pyqtSlot(str)
def setJobName(self, name):
@pyqtSlot(str, bool)
def setJobName(self, name, is_user_specified_job_name = False):
self._is_user_specified_job_name = is_user_specified_job_name
self._job_name = name
self._base_name = name.replace(self._abbr_machine + "_", "")
if name == "":
self._is_user_specified_job_name = False
self.jobNameChanged.emit()
jobNameChanged = pyqtSignal()
@ -295,15 +299,19 @@ class PrintInformation(QObject):
def _updateJobName(self):
if self._base_name == "":
self._job_name = ""
self._job_name = "unnamed"
self._is_user_specified_job_name = False
self.jobNameChanged.emit()
return
base_name = self._stripAccents(self._base_name)
self._setAbbreviatedMachineName()
# Only update the job name when it's not user-specified.
if not self._is_user_specified_job_name:
if self._pre_sliced:
self._job_name = catalog.i18nc("@label", "Pre-sliced file {0}", base_name)
elif Preferences.getInstance().getValue("cura/jobname_prefix"):
elif self._application.getInstance().getPreferences().getValue("cura/jobname_prefix"):
# Don't add abbreviation if it already has the exact same abbreviation.
if base_name.startswith(self._abbr_machine + "_"):
self._job_name = base_name
@ -321,6 +329,8 @@ class PrintInformation(QObject):
baseNameChanged = pyqtSignal()
def setBaseName(self, base_name: str, is_project_file: bool = False):
self._is_user_specified_job_name = False
# Ensure that we don't use entire path but only filename
name = os.path.basename(base_name)
@ -341,17 +351,17 @@ class PrintInformation(QObject):
if is_gcode or is_project_file or (is_empty or (self._base_name == "" and self._base_name != check_name)):
# Only take the file name part, Note : file name might have 'dot' in name as well
data = ''
data = ""
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(name)
data = mime_type.stripExtension(name)
except:
Logger.log("w", "Unsupported Mime Type Database file extension")
Logger.log("w", "Unsupported Mime Type Database file extension %s", name)
if data is not None and check_name is not None:
self._base_name = data
else:
self._base_name = ''
self._base_name = ""
self._updateJobName()
@ -362,7 +372,7 @@ class PrintInformation(QObject):
## Created an acronymn-like abbreviated machine name from the currently active machine name
# Called each time the global stack is switched
def _setAbbreviatedMachineName(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
global_container_stack = self._application.getGlobalContainerStack()
if not global_container_stack:
self._abbr_machine = ""
return

View file

@ -24,7 +24,7 @@ class ConvexHullNode(SceneNode):
self._original_parent = parent
# Color of the drawn convex hull
if Application.getInstance().hasGui():
if not Application.getInstance().getIsHeadLess():
self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
else:
self._color = Color(0, 0, 0)

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from copy import deepcopy
from typing import List
from typing import List, Optional
from UM.Application import Application
from UM.Math.AxisAlignedBox import AxisAlignedBox
@ -13,9 +13,9 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
## Scene nodes that are models are only seen when selecting the corresponding build plate
# Note that many other nodes can just be UM SceneNode objects.
class CuraSceneNode(SceneNode):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if "no_setting_override" not in kwargs:
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False):
super().__init__(parent = parent, visible = visible, name = name)
if not no_setting_override:
self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
self._outside_buildarea = False

View file

@ -1,32 +1,25 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
import os
import urllib.parse
import uuid
from typing import Dict, Union
from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox
from UM.PluginRegistry import PluginRegistry
from UM.SaveFile import SaveFile
from UM.Platform import Platform
from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.i18n import i18nCatalog
from UM.FlameProfiler import pyqtSlot
from UM.Logger import Logger
from UM.Application import Application
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
from UM.Platform import Platform
from UM.SaveFile import SaveFile
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
from UM.MimeTypeDatabase import MimeTypeNotFoundError
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -36,11 +29,17 @@ catalog = i18nCatalog("cura")
# from within QML. We want to be able to trigger things like removing a container
# when a certain action happens. This can be done through this class.
class ContainerManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._application = Application.getInstance()
self._container_registry = ContainerRegistry.getInstance()
def __init__(self, application):
if ContainerManager.__instance is not None:
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
ContainerManager.__instance = self
super().__init__(parent = application)
self._application = application
self._plugin_registry = self._application.getPluginRegistry()
self._container_registry = self._application.getContainerRegistry()
self._machine_manager = self._application.getMachineManager()
self._material_manager = self._application.getMaterialManager()
self._container_name_filters = {}
@ -129,7 +128,7 @@ class ContainerManager(QObject):
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):
for sibbling_container in self._container_registry.findInstanceContainers(base_file = basefile):
if sibbling_container != container:
sibbling_container.setProperty(setting_key, property_name, property_value)
@ -307,13 +306,15 @@ class ContainerManager(QObject):
# \return \type{bool} True if successful, False if not.
@pyqtSlot(result = bool)
def updateQualityChanges(self):
global_stack = Application.getInstance().getGlobalContainerStack()
global_stack = self._machine_manager.activeMachine
if not global_stack:
return False
self._machine_manager.blurSettings.emit()
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
global_stack = self._machine_manager.activeMachine
extruder_stacks = list(global_stack.extruders.values())
for stack in [global_stack] + extruder_stacks:
# Find the quality_changes container for this stack and merge the contents of the top container into it.
quality_changes = stack.qualityChanges
if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
@ -334,13 +335,15 @@ class ContainerManager(QObject):
send_emits_containers = []
# Go through global and extruder stacks and clear their topmost container (the user settings).
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
global_stack = self._machine_manager.activeMachine
extruder_stacks = list(global_stack.extruders.values())
for stack in [global_stack] + extruder_stacks:
container = stack.userChanges
container.clear()
send_emits_containers.append(container)
# user changes are possibly added to make the current setup match the current enabled extruders
Application.getInstance().getMachineManager().correctExtruderSettings()
self._machine_manager.correctExtruderSettings()
for container in send_emits_containers:
container.sendPostponedEmits()
@ -381,21 +384,6 @@ class ContainerManager(QObject):
if container is not None:
container.setMetaDataEntry("GUID", new_guid)
## Get the singleton instance for this class.
@classmethod
def getInstance(cls) -> "ContainerManager":
# Note: Explicit use of class name to prevent issues with inheritance.
if ContainerManager.__instance is None:
ContainerManager.__instance = cls()
return ContainerManager.__instance
__instance = None # type: "ContainerManager"
# Factory function, used by QML
@staticmethod
def createContainerManager(engine, js_engine):
return ContainerManager.getInstance()
def _performMerge(self, merge_into, merge, clear_settings = True):
if merge == merge_into:
return
@ -415,7 +403,7 @@ class ContainerManager(QObject):
serialize_type = ""
try:
plugin_metadata = PluginRegistry.getInstance().getMetaData(plugin_id)
plugin_metadata = self._plugin_registry.getMetaData(plugin_id)
if plugin_metadata:
serialize_type = plugin_metadata["settings_container"]["type"]
else:
@ -470,3 +458,9 @@ class ContainerManager(QObject):
container_list = [n.getContainer() for n in quality_changes_group.getAllNodes() if n.getContainer() is not None]
self._container_registry.exportQualityProfile(container_list, path, file_type)
__instance = None
@classmethod
def getInstance(cls, *args, **kwargs) -> "ContainerManager":
return cls.__instance

View file

@ -475,7 +475,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_definition = extruder_definitions[0]
unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id
extruder_stack = ExtruderStack.ExtruderStack(unique_name, parent = machine)
extruder_stack = ExtruderStack.ExtruderStack(unique_name)
extruder_stack.setName(extruder_definition.getName())
extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))

View file

@ -39,8 +39,8 @@ from . import Exceptions
# This also means that operations on the stack that modifies the container ordering is prohibited and
# will raise an exception.
class CuraContainerStack(ContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
def __init__(self, container_id: str):
super().__init__(container_id)
self._container_registry = ContainerRegistry.getInstance()

View file

@ -7,7 +7,6 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger
from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Machines.VariantManager import VariantType
from .GlobalStack import GlobalStack
@ -29,7 +28,7 @@ class CuraStackBuilder:
variant_manager = application.getVariantManager()
material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager()
registry = ContainerRegistry.getInstance()
registry = application.getContainerRegistry()
definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions:
@ -99,8 +98,7 @@ class CuraStackBuilder:
position = position,
variant_container = extruder_variant_container,
material_container = material_container,
quality_container = application.empty_quality_container,
global_stack = new_global_stack,
quality_container = application.empty_quality_container
)
new_extruder.setNextStack(new_global_stack)
new_global_stack.addExtruder(new_extruder)
@ -139,11 +137,12 @@ class CuraStackBuilder:
@classmethod
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
position: int,
variant_container, material_container, quality_container, global_stack) -> ExtruderStack:
variant_container, material_container, quality_container) -> ExtruderStack:
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
stack = ExtruderStack(new_stack_id, parent = global_stack)
stack = ExtruderStack(new_stack_id)
stack.setName(extruder_definition.getName())
stack.setDefinition(extruder_definition)
@ -162,7 +161,7 @@ class CuraStackBuilder:
# Only add the created containers to the registry after we have set all the other
# properties. This makes the create operation more transactional, since any problems
# setting properties will not result in incomplete containers being added.
ContainerRegistry.getInstance().addContainer(user_container)
registry.addContainer(user_container)
return stack
@ -178,6 +177,7 @@ class CuraStackBuilder:
variant_container, material_container, quality_container) -> GlobalStack:
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
stack = GlobalStack(new_stack_id)
stack.setDefinition(definition)
@ -193,7 +193,7 @@ class CuraStackBuilder:
stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container
ContainerRegistry.getInstance().addContainer(user_container)
registry.addContainer(user_container)
return stack
@ -201,8 +201,10 @@ class CuraStackBuilder:
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
is_global_stack: bool) -> "InstanceContainer":
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
unique_container_name = registry.uniqueName(container_name)
container = InstanceContainer(unique_container_name)
container.setDefinition(definition_id)
@ -217,15 +219,17 @@ class CuraStackBuilder:
@classmethod
def createDefinitionChangesContainer(cls, container_stack, container_name):
from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
unique_container_name = registry.uniqueName(container_name)
definition_changes_container = InstanceContainer(unique_container_name)
definition_changes_container.setDefinition(container_stack.getBottom().getId())
definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
ContainerRegistry.getInstance().addContainer(definition_changes_container)
registry.addContainer(definition_changes_container)
container_stack.definitionChanges = definition_changes_container
return definition_changes_container

View file

@ -15,6 +15,7 @@ from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.SettingInstance import SettingInstance
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from typing import Optional, List, TYPE_CHECKING, Union
if TYPE_CHECKING:
@ -29,6 +30,10 @@ class ExtruderManager(QObject):
## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None):
if ExtruderManager.__instance is not None:
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
ExtruderManager.__instance = self
super().__init__(parent)
self._application = Application.getInstance()
@ -92,28 +97,6 @@ class ExtruderManager(QObject):
if extruder.getId() == extruder_stack_id:
return extruder.qualityChanges.getId()
## The instance of the singleton pattern.
#
# It's None if the extruder manager hasn't been created yet.
__instance = None
@staticmethod
def createExtruderManager():
return ExtruderManager().getInstance()
## Gets an instance of the extruder manager, or creates one if no instance
# exists yet.
#
# This is an implementation of singleton. If an extruder manager already
# exists, it is re-used.
#
# \return The extruder manager.
@classmethod
def getInstance(cls) -> "ExtruderManager":
if not cls.__instance:
cls.__instance = ExtruderManager()
return cls.__instance
## Changes the active extruder by index.
#
# \param index The index of the new active extruder.
@ -557,6 +540,11 @@ class ExtruderManager(QObject):
return result
## Return the default extruder position from the machine manager
@staticmethod
def getDefaultExtruderPosition() -> str:
return Application.getInstance().getMachineManager().defaultExtruderPosition
## Get all extruder values for a certain setting.
#
# This is exposed to qml for display purposes
@ -747,3 +735,9 @@ class ExtruderManager(QObject):
resolved_value = global_stack.getProperty(key, "value", context = context)
return resolved_value
__instance = None
@classmethod
def getInstance(cls, *args, **kwargs) -> "ExtruderManager":
return cls.__instance

View file

@ -25,8 +25,8 @@ if TYPE_CHECKING:
#
#
class ExtruderStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
def __init__(self, container_id: str):
super().__init__(container_id)
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility

View file

@ -23,8 +23,8 @@ from .CuraContainerStack import CuraContainerStack
## Represents the Global or Machine stack and its related containers.
#
class GlobalStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
def __init__(self, container_id: str):
super().__init__(container_id)
self.addMetaDataEntry("type", "machine") # For backward compatibility

View file

@ -17,7 +17,6 @@ from UM.FlameProfiler import pyqtSlot
from UM import Util
from UM.Application import Application
from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.Message import Message
@ -98,12 +97,12 @@ class MachineManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
self.activeStackChanged.connect(self.activeStackValueChanged)
Preferences.getInstance().addPreference("cura/active_machine", "")
self._application.getPreferences().addPreference("cura/active_machine", "")
self._global_event_keys = set()
self._printer_output_devices = [] # type: List[PrinterOutputDevice]
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
# There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged()
@ -164,14 +163,14 @@ class MachineManager(QObject):
rootMaterialChanged = pyqtSignal()
def setInitialActiveMachine(self) -> None:
active_machine_id = Preferences.getInstance().getValue("cura/active_machine")
active_machine_id = self._application.getPreferences().getValue("cura/active_machine")
if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
# An active machine was saved, so restore it.
self.setActiveMachine(active_machine_id)
def _onOutputDevicesChanged(self) -> None:
self._printer_output_devices = []
for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
for printer_output_device in self._application.getOutputDeviceManager().getOutputDevices():
if isinstance(printer_output_device, PrinterOutputDevice):
self._printer_output_devices.append(printer_output_device)
@ -238,7 +237,7 @@ class MachineManager(QObject):
extruder_stack.containersChanged.disconnect(self._onContainersChanged)
# Update the local global container stack reference
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
self._global_container_stack = self._application.getGlobalContainerStack()
if self._global_container_stack:
self.updateDefaultExtruder()
self.updateNumberExtrudersEnabled()
@ -246,7 +245,7 @@ class MachineManager(QObject):
# after switching the global stack we reconnect all the signals and set the variant and material references
if self._global_container_stack:
Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId())
self._application.getPreferences().setValue("cura/active_machine", self._global_container_stack.getId())
self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
self._global_container_stack.containersChanged.connect(self._onContainersChanged)
@ -270,7 +269,7 @@ class MachineManager(QObject):
if self._global_container_stack.getId() in self.machine_extruder_material_update_dict:
for func in self.machine_extruder_material_update_dict[self._global_container_stack.getId()]:
Application.getInstance().callLater(func)
self._application.callLater(func)
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
self.activeQualityGroupChanged.emit()
@ -364,7 +363,7 @@ class MachineManager(QObject):
return # We're done here
ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder
self._global_container_stack = global_stack
Application.getInstance().setGlobalContainerStack(global_stack)
self._application.setGlobalContainerStack(global_stack)
ExtruderManager.getInstance()._globalContainerStackChanged()
self._initMachineState(containers[0])
self._onGlobalContainerChanged()
@ -655,6 +654,15 @@ class MachineManager(QObject):
return ""
@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantId(self) -> str:
if self._active_container_stack:
variant = self._active_container_stack.variant
if variant:
return variant.getId()
return ""
@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantBuildplateName(self) -> str:
if self._global_container_stack:
@ -838,7 +846,7 @@ class MachineManager(QObject):
## Set the amount of extruders on the active machine (global stack)
# \param extruder_count int the number of extruders to set
def setActiveMachineExtruderCount(self, extruder_count: int) -> None:
extruder_manager = Application.getInstance().getExtruderManager()
extruder_manager = self._application.getExtruderManager()
definition_changes_container = self._global_container_stack.definitionChanges
if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container:
@ -855,7 +863,7 @@ class MachineManager(QObject):
self.correctExtruderSettings()
# Check to see if any objects are set to print with an extruder that will no longer exist
root_node = Application.getInstance().getController().getScene().getRoot()
root_node = self._application.getController().getScene().getRoot()
for node in DepthFirstIterator(root_node):
if node.getMeshData():
extruder_nr = node.callDecoration("getActiveExtruderPosition")
@ -888,7 +896,7 @@ class MachineManager(QObject):
global_user_container.removeInstance(setting_key)
# Signal that the global stack has changed
Application.getInstance().globalContainerStackChanged.emit()
self._application.globalContainerStackChanged.emit()
self.forceUpdateAllSettings()
@pyqtSlot(int, result = QObject)
@ -982,6 +990,14 @@ class MachineManager(QObject):
container = extruder.userChanges
container.setProperty(setting_name, property_name, property_value)
## Reset all setting properties of a setting for all extruders.
# \param setting_name The ID of the setting to reset.
@pyqtSlot(str)
def resetSettingForAllExtruders(self, setting_name: str) -> None:
for key, extruder in self._global_container_stack.extruders.items():
container = extruder.userChanges
container.removeInstance(setting_name)
@pyqtProperty("QVariantList", notify = globalContainerChanged)
def currentExtruderPositions(self) -> List[str]:
if self._global_container_stack is None:
@ -1032,6 +1048,10 @@ class MachineManager(QObject):
self.activeQualityChangesGroupChanged.emit()
def _setQualityGroup(self, quality_group, empty_quality_changes: bool = True) -> None:
if quality_group is None:
self._setEmptyQuality()
return
if quality_group.node_for_global.getContainer() is None:
return
for node in quality_group.nodes_for_extruders.values():
@ -1042,10 +1062,6 @@ class MachineManager(QObject):
if empty_quality_changes:
self._current_quality_changes_group = None
if quality_group is None:
self._setEmptyQuality()
return
# Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes:
@ -1120,7 +1136,7 @@ class MachineManager(QObject):
def _setGlobalVariant(self, container_node):
self._global_container_stack.variant = container_node.getContainer()
if not self._global_container_stack.variant:
self._global_container_stack.variant = Application.getInstance().empty_variant_container
self._global_container_stack.variant = self._application.empty_variant_container
def _setMaterial(self, position, container_node = None):
if container_node and container_node.getContainer():
@ -1280,6 +1296,10 @@ class MachineManager(QObject):
self._global_container_stack.variant = self._empty_variant_container
self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
@ -1331,6 +1351,10 @@ class MachineManager(QObject):
self._setMaterial(position, container_node)
self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
@pyqtSlot(str, str)
def setVariantByName(self, position: str, variant_name: str) -> None:
machine_definition_id = self._global_container_stack.definition.id
@ -1346,6 +1370,10 @@ class MachineManager(QObject):
self._updateMaterialWithVariant(position)
self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
if self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
@pyqtSlot(str)
def setQualityGroupByQualityType(self, quality_type: str) -> None:
if self._global_container_stack is None:
@ -1362,7 +1390,7 @@ class MachineManager(QObject):
self._setQualityGroup(quality_group)
# See if we need to show the Discard or Keep changes screen
if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
if not no_dialog and self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
@ -1376,7 +1404,7 @@ class MachineManager(QObject):
self._setQualityChangesGroup(quality_changes_group)
# See if we need to show the Discard or Keep changes screen
if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
if not no_dialog and self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1:
self._application.discardOrKeepProfileChanges()
@pyqtSlot()

103
cura/SingleInstance.py Normal file
View file

@ -0,0 +1,103 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import json
import os
from typing import List, Optional
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
from UM.Logger import Logger
class SingleInstance:
def __init__(self, application, files_to_open: Optional[List[str]]):
self._application = application
self._files_to_open = files_to_open
self._single_instance_server = None
# Starts a client that checks for a single instance server and sends the files that need to opened if the server
# exists. Returns True if the single instance server is found, otherwise False.
def startClient(self) -> bool:
Logger.log("i", "Checking for the presence of an ready running Cura instance.")
single_instance_socket = QLocalSocket(self._application)
Logger.log("d", "Full single instance server name: %s", single_instance_socket.fullServerName())
single_instance_socket.connectToServer("ultimaker-cura")
single_instance_socket.waitForConnected(msecs = 3000) # wait for 3 seconds
if single_instance_socket.state() != QLocalSocket.ConnectedState:
return False
# We only send the files that need to be opened.
if not self._files_to_open:
Logger.log("i", "No file need to be opened, do nothing.")
return True
if single_instance_socket.state() == QLocalSocket.ConnectedState:
Logger.log("i", "Connection has been made to the single-instance Cura socket.")
# Protocol is one line of JSON terminated with a carriage return.
# "command" field is required and holds the name of the command to execute.
# Other fields depend on the command.
payload = {"command": "clear-all"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))
payload = {"command": "focus"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))
for filename in self._files_to_open:
payload = {"command": "open", "filePath": os.path.abspath(filename)}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))
payload = {"command": "close-connection"}
single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii"))
single_instance_socket.flush()
single_instance_socket.waitForDisconnected()
return True
def startServer(self) -> None:
self._single_instance_server = QLocalServer()
self._single_instance_server.newConnection.connect(self._onClientConnected)
self._single_instance_server.listen("ultimaker-cura")
def _onClientConnected(self):
Logger.log("i", "New connection recevied on our single-instance server")
connection = self._single_instance_server.nextPendingConnection()
if connection is not None:
connection.readyRead.connect(lambda c = connection: self.__readCommands(c))
def __readCommands(self, connection):
line = connection.readLine()
while len(line) != 0: # There is also a .canReadLine()
try:
payload = json.loads(str(line, encoding = "ascii").strip())
command = payload["command"]
# Command: Remove all models from the build plate.
if command == "clear-all":
self._application.callLater(lambda: self._application.deleteAll())
# Command: Load a model file
elif command == "open":
self._application.callLater(lambda f = payload["filePath"]: self._application._openFile(f))
# Command: Activate the window and bring it to the top.
elif command == "focus":
# Operating systems these days prevent windows from moving around by themselves.
# 'alert' or flashing the icon in the taskbar is the best thing we do now.
self._application.callLater(lambda: self._application.getMainWindow().alert(0))
# Command: Close the socket connection. We're done.
elif command == "close-connection":
connection.close()
else:
Logger.log("w", "Received an unrecognized command " + str(command))
except json.decoder.JSONDecodeError as ex:
Logger.log("w", "Unable to parse JSON command '%s': %s", line, repr(ex))
line = connection.readLine()

View file

@ -1,9 +1,10 @@
#!/usr/bin/env python3
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import argparse
import faulthandler
import os
import sys
@ -27,7 +28,7 @@ known_args = vars(parser.parse_known_args()[0])
if not known_args["debug"]:
def get_cura_dir_path():
if Platform.isWindows():
return os.path.expanduser("~/AppData/Roaming/cura/")
return os.path.expanduser("~/AppData/Roaming/cura")
elif Platform.isLinux():
return os.path.expanduser("~/.local/share/cura")
elif Platform.isOSX():
@ -39,13 +40,10 @@ if not known_args["debug"]:
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")
sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w", encoding = "utf-8")
import platform
import faulthandler
#WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
# WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
linux_distro_name = platform.linux_distribution()[0].lower()
# The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
try:
import ctypes
@ -79,6 +77,7 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u
sys.path.remove(PATH_real)
sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0.
def exceptHook(hook_type, value, traceback):
from cura.CrashHandler import CrashHandler
from cura.CuraApplication import CuraApplication
@ -121,25 +120,25 @@ def exceptHook(hook_type, value, traceback):
_crash_handler.early_crash_dialog.show()
sys.exit(application.exec_())
if not known_args["debug"]:
sys.excepthook = exceptHook
# Set exception hook to use the crash dialog handler
sys.excepthook = exceptHook
# Enable dumping traceback for all threads
faulthandler.enable(all_threads = True)
# Workaround for a race condition on certain systems where there
# is a race condition between Arcus and PyQt. Importing Arcus
# first seems to prevent Sip from going into a state where it
# tries to create PyQt objects on a non-main thread.
import Arcus #@UnusedImport
import cura.CuraApplication
import cura.Settings.CuraContainerRegistry
from cura.CuraApplication import CuraApplication
faulthandler.enable()
app = CuraApplication()
app.addCommandLineOptions()
app.parseCliOptions()
app.initialize()
# Force an instance of CuraContainerRegistry to be created and reused later.
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance()
app.startSplashWindowPhase()
app.startPostSplashWindowPhase()
# This pre-start up check is needed to determine if we should start the application at all.
if not cura.CuraApplication.CuraApplication.preStartUp(parser = parser, parsed_command_line = known_args):
sys.exit(0)
app = cura.CuraApplication.CuraApplication.getInstance(parser = parser, parsed_command_line = known_args)
app.run()

View file

@ -27,14 +27,6 @@ from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-cura-project-file",
comment = "Cura Project File",
suffixes = ["curaproject.3mf"]
)
)
try:
if not MYPY:
import xml.etree.cElementTree as ET
@ -42,10 +34,20 @@ except ImportError:
Logger.log("w", "Unable to load cElementTree, switching to slower version")
import xml.etree.ElementTree as ET
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
class ThreeMFReader(MeshReader):
def __init__(self):
super().__init__()
def __init__(self, application):
super().__init__(application)
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
comment="3MF",
suffixes=["3mf"]
)
)
self._supported_extensions = [".3mf"]
self._root = None
self._base_name = ""
@ -158,7 +160,7 @@ class ThreeMFReader(MeshReader):
um_node.addDecorator(sliceable_decorator)
return um_node
def read(self, file_name):
def _read(self, file_name):
result = []
self._object_count = 0 # Used to name objects as there is no node name yet.
# The base object of 3mf is a zipped archive.

View file

@ -4,7 +4,6 @@
from configparser import ConfigParser
import zipfile
import os
import threading
from typing import List, Tuple
@ -21,7 +20,7 @@ from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Job import Job
from UM.Preferences import Preferences
@ -84,6 +83,15 @@ class ExtruderInfo:
class ThreeMFWorkspaceReader(WorkspaceReader):
def __init__(self):
super().__init__()
MimeTypeDatabase.addMimeType(
MimeType(
name="application/x-cura-project-file",
comment="Cura Project File",
suffixes=["curaproject.3mf"]
)
)
self._supported_extensions = [".3mf"]
self._dialog = WorkspaceDialog()
self._3mf_mesh_reader = None
@ -456,7 +464,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
num_visible_settings = len(visible_settings_string.split(";"))
active_mode = temp_preferences.getValue("cura/active_mode")
if not active_mode:
active_mode = Preferences.getInstance().getValue("cura/active_mode")
active_mode = Application.getInstance().getPreferences().getValue("cura/active_mode")
except KeyError:
# If there is no preferences file, it's not a workspace, so notify user of failure.
Logger.log("w", "File %s is not a valid workspace.", file_name)
@ -575,7 +583,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
temp_preferences.deserialize(serialized)
# Copy a number of settings from the temp preferences to the global
global_preferences = Preferences.getInstance()
global_preferences = application.getInstance().getPreferences()
visible_settings = temp_preferences.getValue("general/visible_settings")
if visible_settings is None:

View file

@ -13,8 +13,10 @@ from . import ThreeMFWorkspaceReader
from UM.i18n import i18nCatalog
from UM.Platform import Platform
catalog = i18nCatalog("cura")
def getMetaData() -> Dict:
# Workarround for osx not supporting double file extensions correctly.
if Platform.isOSX():
@ -42,7 +44,7 @@ def getMetaData() -> Dict:
def register(app):
if "3MFReader.ThreeMFReader" in sys.modules:
return {"mesh_reader": ThreeMFReader.ThreeMFReader(),
return {"mesh_reader": ThreeMFReader.ThreeMFReader(app),
"workspace_reader": ThreeMFWorkspaceReader.ThreeMFWorkspaceReader()}
else:
return {}

View file

@ -51,7 +51,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
self._writeContainerToArchive(container, archive)
# Write preferences to archive
original_preferences = Preferences.getInstance() #Copy only the preferences that we use to the workspace.
original_preferences = Application.getInstance().getPreferences() #Copy only the preferences that we use to the workspace.
temp_preferences = Preferences()
for preference in {"general/visible_settings", "cura/active_mode", "cura/categories_expanded"}:
temp_preferences.addPreference(preference, None)

View file

@ -3,7 +3,6 @@
from UM.i18n import i18nCatalog
from UM.Extension import Extension
from UM.Preferences import Preferences
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Version import Version
@ -29,7 +28,7 @@ class ChangeLog(Extension, QObject,):
self._change_logs = None
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
Preferences.getInstance().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
def getChangeLogs(self):
@ -79,12 +78,12 @@ class ChangeLog(Extension, QObject,):
if not self._current_app_version:
return #We're on dev branch.
if Preferences.getInstance().getValue("general/latest_version_changelog_shown") == "master":
if Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown") == "master":
latest_version_shown = Version("0.0.0")
else:
latest_version_shown = Version(Preferences.getInstance().getValue("general/latest_version_changelog_shown"))
latest_version_shown = Version(Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown"))
Preferences.getInstance().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
Application.getInstance().getPreferences().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
# Do not show the changelog when there is no global container stack
# This implies we are running Cura for the first time.

View file

@ -1,3 +1,127 @@
[3.4.0]
*Toolbox
The plugin browser has been remodeled into the Toolbox. Navigation now involves graphical elements such as tiles, which can be clicked for further details.
*Upgradable bundled resources
It is now possible to have multiple versions of bundled resources installed: the bundled version and the downloaded upgrade. If an upgrade in the form of a package is present, the bundled version will not be loaded. If it's not present, Ultimaker Cura will revert to the bundled version.
*Package manager recognizes bundled resources
Bundled packages are now made visible to the CuraPackageMangager. This means the resources are included by default, as well as the "wrapping" of a package, (e.g. package.json) so that the CuraPackageManger and Toolbox recognize them as being installed.
*Retraction combing max distance
New setting for maximum combing travel distance. Combing travel moves longer than this value will use retraction. Contributed by smartavionics.
*Infill support
When enabled, infill will be generated only where it is needed using a specialized support generation algorithm for the internal support structures of a part. Contributed by BagelOrb.
*Print outside perimeter before holes
This prioritizes outside perimeters before printing holes. By printing holes as late as possible, there is a reduced risk of travel moves dislodging them from the build plate. This setting should only have an effect if printing outer before inner walls. Contributed by smartavionics.
*Disable omitting retractions in support
Previous versions had no option to disable omitting retraction moves when printing supports, which could cause issues with third-party machines or materials. An option has been added to disable this. Contributed by BagelOrb.
*Support wall line count
Added setting to configure how many walls to print around supports. Contributed by BagelOrb.
*Maximum combing resolution
Combing travel moves are kept at least 1.5 mm long to prevent buffer underruns.
*Avoid supports when traveling
Added setting to avoid supports when performing travel moves. This minimizes the risk of the print head hitting support material.
*Rewrite cross infill
Experimental setting that allows you to input a path to an image to manipulate the cross infill density. This will overlay that image on your model. Contributed by BagelOrb.
*Backup and restore
Added functionality to backup and restore settings and profiles to cloud using the Cura Backups plugin.
*Auto-select model after import
User can now set preferences for the behavior of selecting a newly imported model or not.
*Settings filter timeout
The settings filter is triggered on enter or after a 500ms timeout when typing a setting to filter.
*Event measurements
Added time measurement to logs for occurrences, including startup time, file load time, number of items on the build plate when slicing, slicing time, and time and performance when moving items on the build plate, for benchmarking purposes.
*Send anonymous data
Disable button on the Send anonymous data popup has changed to a more info button, with further options to enable/disable anonymous data messages.
*Configuration error assistant
Detect and show potential configuration file errors to users, e.g. incorrect files and duplicate files in material or quality profiles, there are several places to check. Information is stored and communicated to the user to prevent crashing in future.
*Disable ensure models are kept apart
Disable "Ensure models are kept apart" by default due to to a change in preference files.
*Prepare and monitor QML files
Created two separate QML files for the Prepare and Monitor stages.
*Hide bed temperature
Option to hide bed temperature when no heated bed is present. Contributed by ngraziano.
*Reprap/Marlin GCODE flavor
RepRap firmware now lists values for all extruders in the "Filament used" GCODE comment. Contributed by smartavionics.
*AutoDesk Inventor integration
Open AutoDesk inventor files (parts, assemblies, drawings) directly into Ultimaker Cura. Contributed by thopiekar.
*Blender integration
Open Blender files directly into Ultimaker Cura. Contributed by thopiekar.
*OpenSCAD integration
Open OpenSCAD files directly into Ultimaker Cura. Contributed by thopiekar.
*FreeCAD integration
Open FreeCAD files directly into Ultimaker Cura. Contributed by thopiekar.
*OctoPrint plugin
New version of the OctoPrint plugin for Ultimaker Cura. Contributed by fieldOfView.
*Cura Backups
Backup and restore your configuration, including settings, materials and plugins, for use across different systems.
*MakePrintable
New version of the MakePrintable plugin.
*Compact Prepare sidebar
Plugin that replaces the sidebar with a more compact variation of the original sidebar. Nozzle and material dropdowns are combined into a single line, the “Check compatibility” link is removed, extruder selection buttons are downsized, recommended and custom mode selection buttons are moved to a combobox at the top, and margins are tweaked. Contributed by fieldOfView.
*PauseAtHeight plugin
Bug fixes and improvements for PauseAtHeight plugin. Plugin now accounts for raft layers when choosing “Pause of layer no.” Now positions the nozzle at x and y values of the next layer when resuming. Contributed by JPFrancoia.
*Bug fixes
- Prime tower purge fix. Prime tower purge now starts away from the center, minimizing the chance of overextrusion and nozzle obstructions. Contributed by BagelOrb.
- Extruder 2 temp via USB. Fixed a bug where temperatures cant be read for a second extruder via USB. Contributed by kirilledelman.
- Move to next object position before bed heat. Print one at a time mode caused waiting for the bed temperature to reach the first layer temperature while the nozzle was still positioned on the top of the last part. This has been fixed so that the nozzle moves to the location of the next part before waiting for heat up. Contributed by smartavionics.
- Non-GCODE USB. Fixed a bug where the USB port doesnt open if printer doesn't support GCODE. Contributed by ohrn.
- Improved wall overlap compensation. Minimizes unexpected behavior on overlap lines, providing smoother results. Contributed by BagelOrb.
- Configuration/sync. Fixes minor issues with the configuration/sync menu, such as text rendering on some OSX systems and untranslatable text. Contributed by fieldOfView.
- Print job name reslice. Fixed behavior where print job name changes back to origin when reslicing.
- Discard/keep. Customized settings don't give an 'discard or keep' dialog when changing material.
- Message box styling. Fixed bugs related to message box styling, such as the progress bar overlapping the button in the Sending Data popup.
- Curaproject naming. Fixed bug related to two "curaprojects" in the file name when saving a project.
- No support on first layers. Fixed a bug related to no support generated causing failed prints when model is floating above build plate.
- False incompatible configuration. Fixed a bug where PrintCore and materials were flagged even though the configurations are compatible.
- Spiralize contour overlaps. Fixed a bug related to spiralize contour overlaps.
- Model saved outside build volume. Fixed a bug that would saved a model to file (GCODE) outside the build volume.
- Filament diameter line width. Adjust filament diameter to calculate line width in the GCODE parser.
- Holes in model surfaces. Fixed a bug where illogical travel moves leave holes in the model surface.
- Nozzle legacy file variant. Fixed crashes caused by loading legacy nozzle variant files.
- Brim wall order. Fixed a bug related to brim wall order. Contributed by smartavionics.
- GCODE reader gaps. Fixed a GCODE reader bug that can create a gap at the start of a spiralized layer.
- Korean translation. Fixed some typos in Korean translation.
- ARM/Mali systems. Graphics pipeline for ARM/Mali fixed. Contributed by jwalt.
- NGC Writer. Fixed missing author for NGC Writer plugin.
- Support blocker legacy GPU. Fixes depth picking on older GPUs that do not support the 4.1 shading model which caused the support blocker to put cubes in unexpected locations. Contributed by fieldOfView.
*Third-party printers
- Felix Tec4 printer. Updated definitions for Felix Tec4. Contributed by kerog777.
- Deltacomb. Updated definitions for Deltacomb. Contributed by kaleidoscopeit.
- Rigid3D Mucit. Added definitions for Rigid3D Mucit. Contributed by Rigid3D.
[3.3.0]
*Profile for the Ultimaker S5
@ -66,7 +190,7 @@ Generate a cube mesh to prevent support material generation in specific areas of
*Real bridging - smartavionics
New experimental feature that detects bridges, adjusting the print speed, slow and fan speed to enhance print quality on bridging parts.
*Updated CuraEngine executable - thopiekar & Ultimaker B.V. ❤️
*Updated CuraEngine executable - thopiekar & Ultimaker B.V.
The CuraEngine executable contains a dedicated icon, author and license info on Windows now. The icon has been designed by Ultimaker B.V.
*Use RapidJSON and ClipperLib from system libraries

View file

@ -4,7 +4,6 @@
from UM.Backend.Backend import Backend, BackendState
from UM.Application import Application
from UM.Scene.SceneNode import SceneNode
from UM.Preferences import Preferences
from UM.Signal import Signal
from UM.Logger import Logger
from UM.Message import Message
@ -72,7 +71,7 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
default_engine_location = os.path.abspath(default_engine_location)
Preferences.getInstance().addPreference("backend/location", default_engine_location)
Application.getInstance().getPreferences().addPreference("backend/location", default_engine_location)
# Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False
@ -121,7 +120,7 @@ class CuraEngineBackend(QObject, Backend):
self._slice_start_time = None
self._is_disabled = False
Preferences.getInstance().addPreference("general/auto_slice", False)
Application.getInstance().getPreferences().addPreference("general/auto_slice", False)
self._use_timer = False
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
@ -131,7 +130,7 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.setSingleShot(True)
self._change_timer.setInterval(500)
self.determineAutoSlicing()
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._application.initializationFinished.connect(self.initialize)
@ -170,7 +169,7 @@ class CuraEngineBackend(QObject, Backend):
# \return list of commands and args / parameters.
def getEngineCommand(self):
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
return [Application.getInstance().getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
## Emitted when we get a message containing print duration and material amount.
# This also implies the slicing has finished.
@ -275,7 +274,7 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(0)
Logger.log("d", "Attempting to kill the engine process")
if Application.getInstance().getCommandLineOption("external-backend", False):
if Application.getInstance().getUseExternalBackend():
return
if self._process is not None:
@ -408,7 +407,7 @@ class CuraEngineBackend(QObject, Backend):
enable_timer = True
self._is_disabled = False
if not Preferences.getInstance().getValue("general/auto_slice"):
if not Application.getInstance().getPreferences().getValue("general/auto_slice"):
enable_timer = False
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isBlockSlicing"):

View file

@ -6,7 +6,6 @@ import gc
from UM.Job import Job
from UM.Application import Application
from UM.Mesh.MeshData import MeshData
from UM.Preferences import Preferences
from UM.View.GL.OpenGLContext import OpenGLContext
from UM.Message import Message
@ -199,7 +198,7 @@ class ProcessSlicedLayersJob(Job):
material_color_map[0, :] = color
# We have to scale the colors for compatibility mode
if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")):
if OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode")):
line_type_brightness = 0.5 # for compatibility mode
else:
line_type_brightness = 1.0

View file

@ -287,14 +287,9 @@ class StartSliceJob(Job):
# \return A dictionary of replacement tokens to the values they should be
# replaced with.
def _buildReplacementTokens(self, stack) -> dict:
default_extruder_position = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
result = {}
for key in stack.getAllKeys():
setting_type = stack.definition.getProperty(key, "type")
value = stack.getProperty(key, "value")
if setting_type == "extruder" and value == -1:
# replace with the default value
value = default_extruder_position
result[key] = value
Job.yieldThread()

View file

@ -5,8 +5,7 @@ from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from UM.Extension import Extension
from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.Application import Application
from UM.i18n import i18nCatalog
from cura.Settings.GlobalStack import GlobalStack
@ -27,12 +26,12 @@ class FirmwareUpdateChecker(Extension):
# Initialize the Preference called `latest_checked_firmware` that stores the last version
# checked for the UM3. In the future if we need to check other printers' firmware
Preferences.getInstance().addPreference("info/latest_checked_firmware", "")
Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "")
# Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the
# 'check for updates' option
Preferences.getInstance().addPreference("info/automatic_update_check", True)
if Preferences.getInstance().getValue("info/automatic_update_check"):
Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True)
if Application.getInstance().getPreferences().getValue("info/automatic_update_check"):
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
self._download_url = None

View file

@ -1,7 +1,6 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Preferences import Preferences
from UM.Application import Application
from UM.Message import Message
from UM.Logger import Logger
@ -51,11 +50,11 @@ class FirmwareUpdateCheckerJob(Job):
current_version = reader(current_version_file).readline().rstrip()
# If it is the first time the version is checked, the checked_version is ''
checked_version = Preferences.getInstance().getValue("info/latest_checked_firmware")
checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware")
# If the checked_version is '', it's because is the first time we check firmware and in this case
# we will not show the notification, but we will store it for the next time
Preferences.getInstance().setValue("info/latest_checked_firmware", current_version)
Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version)
Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version)
# The first time we want to store the current version, the notification will not be shown,
@ -63,13 +62,26 @@ class FirmwareUpdateCheckerJob(Job):
# notify the user when no new firmware version is available.
if (checked_version != "") and (checked_version != current_version):
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
message = Message(i18n_catalog.i18nc("@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(machine_name = machine_name),
title = i18n_catalog.i18nc("@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name)
message.addAction("download", i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]")
message = Message(i18n_catalog.i18nc(
"@info Don't translate {machine_name}, since it gets replaced by a printer name!",
"New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(
machine_name=machine_name),
title=i18n_catalog.i18nc(
"@info:title The %s gets replaced with the printer name.",
"New %s firmware available") % machine_name)
message.addAction("download",
i18n_catalog.i18nc("@action:button", "How to update"),
"[no_icon]",
"[no_description]",
button_style=Message.ActionButtonStyle.LINK,
button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT)
# If we do this in a cool way, the download url should be available in the JSON file
if self._set_download_url_callback:
self._set_download_url_callback("https://ultimaker.com/en/resources/23129-updating-the-firmware?utm_source=cura&utm_medium=software&utm_campaign=hw-update")
self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
message.actionTriggered.connect(self._callback)
message.show()

View file

@ -12,11 +12,11 @@ from UM.PluginRegistry import PluginRegistry
# If you're zipping g-code, you might as well use gzip!
class GCodeGzReader(MeshReader):
def __init__(self):
super().__init__()
def __init__(self, application):
super().__init__(application)
self._supported_extensions = [".gcode.gz"]
def read(self, file_name):
def _read(self, file_name):
with open(file_name, "rb") as file:
file_data = file.read()
uncompressed_gcode = gzip.decompress(file_data).decode("utf-8")

View file

@ -21,4 +21,4 @@ def getMetaData():
def register(app):
app.addNonSliceableExtension(".gz")
return { "mesh_reader": GCodeGzReader.GCodeGzReader() }
return { "mesh_reader": GCodeGzReader.GCodeGzReader(app) }

View file

@ -10,7 +10,6 @@ from UM.Math.Vector import Vector
from UM.Message import Message
from cura.Scene.CuraSceneNode import CuraSceneNode
from UM.i18n import i18nCatalog
from UM.Preferences import Preferences
catalog = i18nCatalog("cura")
@ -47,7 +46,7 @@ class FlavorParser:
self._current_layer_thickness = 0.2 # default
self._filament_diameter = 2.85 # default
Preferences.getInstance().addPreference("gcodereader/show_caution", True)
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
def _clearValues(self) -> None:
self._extruder_number = 0
@ -462,7 +461,7 @@ class FlavorParser:
Logger.log("d", "GCode loading finished")
if Preferences.getInstance().getValue("gcodereader/show_caution"):
if Application.getInstance().getPreferences().getValue("gcodereader/show_caution"):
caution_message = Message(catalog.i18nc(
"@info:generic",
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."),

View file

@ -4,7 +4,7 @@
from UM.FileHandler.FileReader import FileReader
from UM.Mesh.MeshReader import MeshReader
from UM.i18n import i18nCatalog
from UM.Preferences import Preferences
from UM.Application import Application
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
catalog = i18nCatalog("cura")
@ -27,12 +27,12 @@ class GCodeReader(MeshReader):
_flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(),
"Marlin" : MarlinFlavorParser.MarlinFlavorParser()}
def __init__(self):
super(GCodeReader, self).__init__()
def __init__(self, application):
super(GCodeReader, self).__init__(application)
self._supported_extensions = [".gcode", ".g"]
self._flavor_reader = None
Preferences.getInstance().addPreference("gcodereader/show_caution", True)
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
def preReadFromStream(self, stream, *args, **kwargs):
for line in stream.split("\n"):
@ -57,7 +57,7 @@ class GCodeReader(MeshReader):
def readFromStream(self, stream):
return self._flavor_reader.processGCodeStream(stream)
def read(self, file_name):
def _read(self, file_name):
with open(file_name, "r", encoding = "utf-8") as file:
file_data = file.read()
return self.readFromStream(file_data)

View file

@ -23,4 +23,4 @@ def getMetaData():
def register(app):
app.addNonSliceableExtension(".gcode")
app.addNonSliceableExtension(".g")
return { "mesh_reader": GCodeReader.GCodeReader() }
return { "mesh_reader": GCodeReader.GCodeReader(app) }

View file

@ -17,8 +17,8 @@ from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
class ImageReader(MeshReader):
def __init__(self):
super(ImageReader, self).__init__()
def __init__(self, application):
super(ImageReader, self).__init__(application)
self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
self._ui = ImageReaderUI(self)
@ -44,7 +44,7 @@ class ImageReader(MeshReader):
return MeshReader.PreReadResult.cancelled
return MeshReader.PreReadResult.accepted
def read(self, file_name):
def _read(self, file_name):
size = max(self._ui.getWidth(), self._ui.getDepth())
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert)

View file

@ -33,4 +33,4 @@ def getMetaData():
}
def register(app):
return { "mesh_reader": ImageReader.ImageReader() }
return { "mesh_reader": ImageReader.ImageReader(app) }

View file

@ -27,7 +27,7 @@ class ModelChecker(QObject, Extension):
self._caution_message = Message("", #Message text gets set when the message gets shown, to display the models in question.
lifetime = 0,
title = catalog.i18nc("@info:title", "Model Checker Warning"))
title = catalog.i18nc("@info:title", "3D Model Assistant"))
Application.getInstance().initializationFinished.connect(self._pluginsInitialized)
Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged)

View file

@ -5,7 +5,6 @@ from UM.Tool import Tool
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Application import Application
from UM.Preferences import Preferences
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.SettingInstance import SettingInstance
@ -27,7 +26,7 @@ class PerObjectSettingsTool(Tool):
Selection.selectionChanged.connect(self.propertyChanged)
Preferences.getInstance().preferenceChanged.connect(self._onPreferenceChanged)
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferenceChanged)
self._onPreferenceChanged("cura/active_mode")
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
@ -106,7 +105,7 @@ class PerObjectSettingsTool(Tool):
def _onPreferenceChanged(self, preference):
if preference == "cura/active_mode":
self._advanced_mode = Preferences.getInstance().getValue(preference) == 1
self._advanced_mode = Application.getInstance().getPreferences().getValue(preference) == 1
self._updateEnabled()
def _onGlobalContainerChanged(self):

View file

@ -16,7 +16,6 @@ from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Message import Message
from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.Selection import Selection
@ -81,30 +80,30 @@ class SimulationView(View):
self._show_travel_moves = False
self._nozzle_node = None
Preferences.getInstance().addPreference("view/top_layer_count", 5)
Preferences.getInstance().addPreference("view/only_show_top_layers", False)
Preferences.getInstance().addPreference("view/force_layer_view_compatibility_mode", False)
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
Application.getInstance().getPreferences().addPreference("view/force_layer_view_compatibility_mode", False)
Preferences.getInstance().addPreference("layerview/layer_view_type", 0)
Preferences.getInstance().addPreference("layerview/extruder_opacities", "")
Application.getInstance().getPreferences().addPreference("layerview/layer_view_type", 0)
Application.getInstance().getPreferences().addPreference("layerview/extruder_opacities", "")
Preferences.getInstance().addPreference("layerview/show_travel_moves", False)
Preferences.getInstance().addPreference("layerview/show_helpers", True)
Preferences.getInstance().addPreference("layerview/show_skin", True)
Preferences.getInstance().addPreference("layerview/show_infill", True)
Application.getInstance().getPreferences().addPreference("layerview/show_travel_moves", False)
Application.getInstance().getPreferences().addPreference("layerview/show_helpers", True)
Application.getInstance().getPreferences().addPreference("layerview/show_skin", True)
Application.getInstance().getPreferences().addPreference("layerview/show_infill", True)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._updateWithPreferences()
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
self._compatibility_mode = self._evaluateCompatibilityMode()
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
title = catalog.i18nc("@info:title", "Simulation View"))
def _evaluateCompatibilityMode(self):
return OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
return OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode"))
def _resetSettings(self):
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
@ -543,23 +542,23 @@ class SimulationView(View):
self._top_layers_job = None
def _updateWithPreferences(self):
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
self._compatibility_mode = self._evaluateCompatibilityMode()
self.setSimulationViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type"))));
self.setSimulationViewType(int(float(Application.getInstance().getPreferences().getValue("layerview/layer_view_type"))));
for extruder_nr, extruder_opacity in enumerate(Preferences.getInstance().getValue("layerview/extruder_opacities").split("|")):
for extruder_nr, extruder_opacity in enumerate(Application.getInstance().getPreferences().getValue("layerview/extruder_opacities").split("|")):
try:
opacity = float(extruder_opacity)
except ValueError:
opacity = 1.0
self.setExtruderOpacity(extruder_nr, opacity)
self.setShowTravelMoves(bool(Preferences.getInstance().getValue("layerview/show_travel_moves")))
self.setShowHelpers(bool(Preferences.getInstance().getValue("layerview/show_helpers")))
self.setShowSkin(bool(Preferences.getInstance().getValue("layerview/show_skin")))
self.setShowInfill(bool(Preferences.getInstance().getValue("layerview/show_infill")))
self.setShowTravelMoves(bool(Application.getInstance().getPreferences().getValue("layerview/show_travel_moves")))
self.setShowHelpers(bool(Application.getInstance().getPreferences().getValue("layerview/show_helpers")))
self.setShowSkin(bool(Application.getInstance().getPreferences().getValue("layerview/show_skin")))
self.setShowInfill(bool(Application.getInstance().getPreferences().getValue("layerview/show_infill")))
self._startUpdateTopLayers()
self.preferencesChanged.emit()

View file

@ -10,7 +10,6 @@ from PyQt5.QtCore import pyqtSlot, QObject
from UM.Extension import Extension
from UM.Application import Application
from UM.Preferences import Preferences
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Message import Message
from UM.i18n import i18nCatalog
@ -34,22 +33,23 @@ class SliceInfo(QObject, Extension):
QObject.__init__(self, parent)
Extension.__init__(self)
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
Preferences.getInstance().addPreference("info/send_slice_info", True)
Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
Application.getInstance().getPreferences().addPreference("info/send_slice_info", True)
Application.getInstance().getPreferences().addPreference("info/asked_send_slice_info", False)
self._more_info_dialog = None
self._example_data_content = None
if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
if not Application.getInstance().getPreferences().getValue("info/asked_send_slice_info"):
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."),
lifetime = 0,
dismissable = False,
title = catalog.i18nc("@info:title", "Collecting Data"))
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None,
description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK)
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show()
@ -62,7 +62,7 @@ class SliceInfo(QObject, Extension):
## Perform action based on user input.
# Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it.
def messageActionTriggered(self, message_id, action_id):
Preferences.getInstance().setValue("info/asked_send_slice_info", True)
Application.getInstance().getPreferences().setValue("info/asked_send_slice_info", True)
if action_id == "MoreInfo":
self.showMoreInfoDialog()
self.send_slice_info_message.hide()
@ -88,11 +88,11 @@ class SliceInfo(QObject, Extension):
@pyqtSlot(bool)
def setSendSliceInfo(self, enabled: bool):
Preferences.getInstance().setValue("info/send_slice_info", enabled)
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
def _onWriteStarted(self, output_device):
try:
if not Preferences.getInstance().getValue("info/send_slice_info"):
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
Logger.log("d", "'info/send_slice_info' is turned off.")
return # Do nothing, user does not want to send data
@ -107,7 +107,7 @@ class SliceInfo(QObject, Extension):
data["schema_version"] = 0
data["cura_version"] = application.getVersion()
active_mode = Preferences.getInstance().getValue("cura/active_mode")
active_mode = Application.getInstance().getPreferences().getValue("cura/active_mode")
if active_mode == 0:
data["active_mode"] = "recommended"
else:
@ -122,7 +122,7 @@ class SliceInfo(QObject, Extension):
machine_settings_changed_by_user = True
data["machine_settings_changed_by_user"] = machine_settings_changed_by_user
data["language"] = Preferences.getInstance().getValue("general/language")
data["language"] = Application.getInstance().getPreferences().getValue("general/language")
data["os"] = {"type": platform.system(), "version": platform.version()}
data["active_machine"] = {"definition_id": global_stack.definition.getId(),

View file

@ -6,7 +6,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.Selection import Selection
from UM.Resources import Resources
from UM.Application import Application
from UM.Preferences import Preferences
from UM.View.RenderBatch import RenderBatch
from UM.Settings.Validator import ValidatorState
from UM.Math.Color import Color
@ -23,7 +22,7 @@ class SolidView(View):
def __init__(self):
super().__init__()
Preferences.getInstance().addPreference("view/show_overhang", True)
Application.getInstance().getPreferences().addPreference("view/show_overhang", True)
self._enabled_shader = None
self._disabled_shader = None
@ -65,7 +64,7 @@ class SolidView(View):
support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)
if support_angle_stack is not None and Preferences.getInstance().getValue("view/show_overhang"):
if support_angle_stack is not None and Application.getInstance().getPreferences().getValue("view/show_overhang"):
angle = support_angle_stack.getProperty("support_angle", "value")
# Make sure the overhang angle is valid before passing it to the shader
# Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)

View file

@ -31,8 +31,9 @@ Item
frameVisible: false
selectionMode: 0
model: packageData.supported_configs
headerDelegate: Item
headerDelegate: Rectangle
{
color: UM.Theme.getColor("sidebar")
height: UM.Theme.getSize("toolbox_chart_row").height
Label
{

View file

@ -34,6 +34,7 @@ Column
// Don't allow installing while another download is running
enabled: installed || !(toolbox.isDownloading && toolbox.activePackage != model)
opacity: enabled ? 1.0 : 0.5
visible: !updateButton.visible // Don't show when the update button is visible
}
ToolboxProgressButton
@ -55,7 +56,7 @@ Column
// Don't allow installing while another download is running
enabled: !(toolbox.isDownloading && toolbox.activePackage != model)
opacity: enabled ? 1.0 : 0.5
visible: installed && canUpdate
visible: canUpdate
}
Connections
{

View file

@ -12,6 +12,7 @@ Column
height: childrenRect.height
width: parent.width
spacing: UM.Theme.getSize("default_margin").height
/* Hidden for 3.4
Label
{
id: heading
@ -20,6 +21,7 @@ Column
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
*/
GridLayout
{
id: grid

View file

@ -18,6 +18,7 @@ ScrollView
spacing: UM.Theme.getSize("default_margin").height
padding: UM.Theme.getSize("wide_margin").height
height: childrenRect.height + 2 * padding
ToolboxDownloadsShowcase
{
id: showcase
@ -29,6 +30,7 @@ ScrollView
width: parent.width
height: UM.Theme.getSize("default_lining").height
}
ToolboxDownloadsGrid
{
id: allPlugins

View file

@ -19,10 +19,11 @@ Column
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Row
Grid
{
height: childrenRect.height
spacing: UM.Theme.getSize("wide_margin").width
columns: 3
anchors
{
horizontalCenter: parent.horizontalCenter

View file

@ -33,6 +33,7 @@ Item
toolbox.viewPage = "overview"
}
}
ToolboxTabButton
{
text: catalog.i18nc("@title:tab", "Materials")

View file

@ -16,7 +16,7 @@ Item
{
color: UM.Theme.getColor("lining")
width: parent.width
height: UM.Theme.getSize("default_lining").height
height: Math.floor(UM.Theme.getSize("default_lining").height)
anchors.bottom: parent.bottom
}
Row
@ -40,14 +40,14 @@ Item
Column
{
id: pluginInfo
topPadding: UM.Theme.getSize("default_margin").height / 2
topPadding: Math.floor(UM.Theme.getSize("default_margin").height / 2)
property var color: model.type === "plugin" && !isEnabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("text")
width: tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0))
width: Math.floor(tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0)))
Label
{
text: model.name
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold")
color: pluginInfo.color
@ -81,7 +81,7 @@ Item
}
}
width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height
height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft

View file

@ -13,6 +13,16 @@ Column
width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height
Label
{
visible: !model.is_installed
text: catalog.i18nc("@label", "Will install upon restarting")
color: UM.Theme.getColor("lining")
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
width: parent.width
}
ToolboxProgressButton
{
id: updateButton
@ -39,7 +49,7 @@ Column
{
id: removeButton
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall")
visible: !model.is_bundled
visible: !model.is_bundled && model.is_installed
enabled: !toolbox.isDownloading
style: ButtonStyle
{

View file

@ -150,7 +150,7 @@ Item
{
id: loader
visible: active
source: "../images/loading.gif"
source: visible ? "../images/loading.gif" : ""
width: UM.Theme.getSize("toolbox_loader").width
height: UM.Theme.getSize("toolbox_loader").height
anchors.right: button.left

View file

@ -29,8 +29,9 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "is_enabled")
self.addRoleName(Qt.UserRole + 15, "has_configs")
self.addRoleName(Qt.UserRole + 16, "supported_configs")
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
self.addRoleName(Qt.UserRole + 16, "has_configs")
self.addRoleName(Qt.UserRole + 17, "supported_configs")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str]
@ -73,6 +74,7 @@ class PackagesModel(ListModel):
"last_updated": package["last_updated"] if "last_updated" in package else None,
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
"is_enabled": package["is_enabled"] if "is_enabled" in package else False,
"is_installed": package["is_installed"] if "is_installed" in package else False,
"has_configs": has_configs,
"supported_configs": configs_model
})

View file

@ -28,7 +28,8 @@ i18n_catalog = i18nCatalog("cura")
## The Toolbox class is responsible of communicating with the server through the API
class Toolbox(QObject, Extension):
DEFAULT_PACKAGES_API_ROOT = "https://api.ultimaker.com"
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com"
DEFAULT_CLOUD_API_VERSION = 1
def __init__(self, parent=None) -> None:
super().__init__(parent)
@ -36,14 +37,11 @@ class Toolbox(QObject, Extension):
self._application = Application.getInstance()
self._package_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry()
self._packages_api_root = self._getPackagesApiRoot()
self._packages_version = self._getPackagesVersion()
self._api_version = 1
self._api_url = "{api_root}/cura-packages/v{api_version}/cura/v{package_version}".format(
api_root = self._packages_api_root,
api_version = self._api_version,
package_version = self._packages_version
)
self._sdk_version = None
self._cloud_api_version = None
self._cloud_api_root = None
self._api_url = None
# Network:
self._get_packages_request = None
@ -64,21 +62,19 @@ class Toolbox(QObject, Extension):
)
)
]
self._request_urls = {
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url))
}
self._request_urls = {}
self._to_update = [] # Package_ids that are waiting to be updated
self._old_plugin_ids = []
# Data:
self._metadata = {
"authors": [],
"packages": [],
"plugins_showcase": [],
"plugins_available": [],
"plugins_installed": [],
"materials_showcase": [],
"materials_available": [],
"materials_installed": []
}
@ -161,22 +157,52 @@ class Toolbox(QObject, Extension):
# this is initialized. Therefore, we wait until the application is ready.
def _onAppInitialized(self) -> None:
self._package_manager = Application.getInstance().getPackageManager()
self._sdk_version = self._getSDKVersion()
self._cloud_api_version = self._getCloudAPIVersion()
self._cloud_api_root = self._getCloudAPIRoot()
self._api_url = "{cloud_api_root}/cura-packages/v{cloud_api_version}/cura/v{sdk_version}".format(
cloud_api_root=self._cloud_api_root,
cloud_api_version=self._cloud_api_version,
sdk_version=self._sdk_version
)
self._request_urls = {
"authors": QUrl("{base_url}/authors".format(base_url=self._api_url)),
"packages": QUrl("{base_url}/packages".format(base_url=self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
"plugins_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url=self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
"materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url=self._api_url))
}
# Get the API root for the packages API depending on Cura version settings.
def _getPackagesApiRoot(self) -> str:
def _getCloudAPIRoot(self) -> str:
if not hasattr(cura, "CuraVersion"):
return self.DEFAULT_PACKAGES_API_ROOT
if not hasattr(cura.CuraVersion, "CuraPackagesApiRoot"):
return self.DEFAULT_PACKAGES_API_ROOT
return cura.CuraVersion.CuraPackagesApiRoot
return self.DEFAULT_CLOUD_API_ROOT
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"):
return self.DEFAULT_CLOUD_API_ROOT
if not cura.CuraVersion.CuraCloudAPIRoot:
return self.DEFAULT_CLOUD_API_ROOT
return cura.CuraVersion.CuraCloudAPIRoot
# Get the cloud API version from CuraVersion
def _getCloudAPIVersion(self) -> int:
if not hasattr(cura, "CuraVersion"):
return self.DEFAULT_CLOUD_API_VERSION
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"):
return self.DEFAULT_CLOUD_API_VERSION
if not cura.CuraVersion.CuraCloudAPIVersion:
return self.DEFAULT_CLOUD_API_VERSION
return cura.CuraVersion.CuraCloudAPIVersion
# Get the packages version depending on Cura version settings.
def _getPackagesVersion(self) -> int:
def _getSDKVersion(self) -> int:
if not hasattr(cura, "CuraVersion"):
return self._plugin_registry.APIVersion
if not hasattr(cura.CuraVersion, "CuraPackagesVersion"):
if not hasattr(cura.CuraVersion, "CuraSDKVersion"):
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraPackagesVersion
if not cura.CuraVersion.CuraSDKVersion:
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraSDKVersion
@pyqtSlot()
def browsePackages(self) -> None:
@ -212,15 +238,52 @@ class Toolbox(QObject, Extension):
dialog = Application.getInstance().createQmlComponent(path, {"toolbox": self})
return dialog
def _convertPluginMetadata(self, plugin: dict) -> dict:
formatted = {
"package_id": plugin["id"],
"package_type": "plugin",
"display_name": plugin["plugin"]["name"],
"package_version": plugin["plugin"]["version"],
"sdk_version": plugin["plugin"]["api"],
"author": {
"author_id": plugin["plugin"]["author"],
"display_name": plugin["plugin"]["author"]
},
"is_installed": True,
"description": plugin["plugin"]["description"]
}
return formatted
@pyqtSlot()
def _updateInstalledModels(self) -> None:
# This is moved here to avoid code duplication and so that after installing plugins they get removed from the
# list of old plugins
old_plugin_ids = self._plugin_registry.getInstalledPlugins()
installed_package_ids = self._package_manager.getAllInstalledPackageIDs()
self._old_plugin_ids = []
self._old_plugin_metadata = []
for plugin_id in old_plugin_ids:
if plugin_id not in installed_package_ids:
Logger.log('i', 'Found a plugin that was installed with the old plugin browser: %s', plugin_id)
old_metadata = self._plugin_registry.getMetaData(plugin_id)
new_metadata = self._convertPluginMetadata(old_metadata)
self._old_plugin_ids.append(plugin_id)
self._old_plugin_metadata.append(new_metadata)
all_packages = self._package_manager.getAllInstalledPackagesInfo()
if "plugin" in all_packages:
self._metadata["plugins_installed"] = all_packages["plugin"]
self._metadata["plugins_installed"] = all_packages["plugin"] + self._old_plugin_metadata
self._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
self.metadataChanged.emit()
if "material" in all_packages:
self._metadata["materials_installed"] = all_packages["material"]
# TODO: ADD MATERIALS HERE ONCE MATERIALS PORTION OF TOOLBOX IS LIVE
self._models["materials_installed"].setMetadata(self._metadata["materials_installed"])
self.metadataChanged.emit()
@ -250,8 +313,6 @@ class Toolbox(QObject, Extension):
if remote_package:
download_url = remote_package["download_url"]
Logger.log("d", "Updating package [%s]..." % plugin_id)
if self._package_manager.isUserInstalledPackage(plugin_id):
self.uninstall(plugin_id)
self.startDownload(download_url)
else:
Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id)
@ -306,6 +367,9 @@ class Toolbox(QObject, Extension):
# --------------------------------------------------------------------------
@pyqtSlot(str, result = bool)
def canUpdate(self, package_id: str) -> bool:
if self.isOldPlugin(package_id):
return True
local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None:
return False
@ -318,19 +382,21 @@ class Toolbox(QObject, Extension):
remote_version = Version(remote_package["package_version"])
return remote_version > local_version
@pyqtSlot(str, result=bool)
@pyqtSlot(str, result = bool)
def canDowngrade(self, package_id: str) -> bool:
# If the currently installed version is higher than the bundled version (if present), the we can downgrade
# this package.
local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None:
return False
remote_package = self.getRemotePackage(package_id)
if remote_package is None:
bundled_package = self._package_manager.getBundledPackageInfo(package_id)
if bundled_package is None:
return False
local_version = Version(local_package["package_version"])
remote_version = Version(remote_package["package_version"])
return remote_version < local_version
bundled_version = Version(bundled_package["package_version"])
return bundled_version < local_version
@pyqtSlot(str, result = bool)
def isInstalled(self, package_id: str) -> bool:
@ -342,6 +408,13 @@ class Toolbox(QObject, Extension):
return True
return False
# Check for plugins that were installed with the old plugin browser
@pyqtSlot(str, result = bool)
def isOldPlugin(self, plugin_id: str) -> bool:
if plugin_id in self._old_plugin_ids:
return True
return False
def loadingComplete(self) -> bool:
populated = 0
for list in self._metadata.items():
@ -383,7 +456,10 @@ class Toolbox(QObject, Extension):
def resetDownload(self) -> None:
if self._download_reply:
try:
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
except TypeError: #Raised when the method is not connected to the signal yet.
pass #Don't need to disconnect.
self._download_reply.abort()
self._download_reply = None
self._download_request = None

View file

@ -148,6 +148,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def selectPrinter(self, target_printer: str = "") -> None:
self._sending_job.send(target_printer)
@pyqtSlot()
def cancelPrintSelection(self) -> None:
self._sending_gcode = False
## Greenlet to send a job to the printer over the network.
#
# This greenlet gets called asynchronously in requestWrite. It is a
@ -388,7 +392,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._updatePrintJob(print_job, print_job_data)
if print_job.state != "queued": # Print job should be assigned to a printer.
if print_job.state in ["failed", "finished", "aborted"]:
if print_job.state in ["failed", "finished", "aborted", "none"]:
# Print job was already completed, so don't attach it to a printer.
printer = None
else:

View file

@ -90,6 +90,7 @@ UM.Dialog
onClicked: {
base.visible = false;
printerSelectionCombobox.currentIndex = 0
OutputDevice.cancelPrintSelection()
}
}
]

View file

@ -5,7 +5,6 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.Logger import Logger
from UM.Application import Application
from UM.Signal import Signal, signalemitter
from UM.Preferences import Preferences
from UM.Version import Version
from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
@ -54,7 +53,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
# Get list of manual instances from preferences
self._preferences = Preferences.getInstance()
self._preferences = Application.getInstance().getPreferences()
self._preferences.addPreference("um3networkprinting/manual_instances",
"") # A comma-separated list of ip adresses or hostnames

View file

@ -379,13 +379,14 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
def resumePrint(self):
self._paused = False
self._sendNextGcodeLine() #Send one line of g-code next so that we'll trigger an "ok" response loop even if we're not polling temperatures.
def cancelPrint(self):
self._gcode_position = 0
self._gcode.clear()
self._printers[0].updateActivePrintJob(None)
self._is_printing = False
self._is_paused = False
self._paused = False
# Turn off temperatures, fan and steppers
self._sendCommand("M140 S0")

View file

@ -1,10 +1,16 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Signal import Signal, signalemitter
from UM.Application import Application
from UM.Resources import Resources
import threading
import platform
import time
import serial.tools.list_ports
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger
from UM.Resources import Resources
from UM.Signal import Signal, signalemitter
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.i18n import i18nCatalog
@ -12,12 +18,6 @@ from cura.PrinterOutputDevice import ConnectionState
from cura.CuraApplication import CuraApplication
from . import USBPrinterOutputDevice
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
import threading
import platform
import time
import serial.tools.list_ports
i18n_catalog = i18nCatalog("cura")
@ -28,8 +28,14 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
addUSBOutputDeviceSignal = Signal()
progressChanged = pyqtSignal()
def __init__(self, parent = None):
def __init__(self, application, parent = None):
if USBPrinterOutputDeviceManager.__instance is not None:
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
USBPrinterOutputDeviceManager.__instance = self
super().__init__(parent = parent)
self._application = application
self._serial_port_list = []
self._usb_output_devices = {}
self._usb_output_devices_model = None
@ -38,11 +44,11 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._check_updates = True
Application.getInstance().applicationShuttingDown.connect(self.stop)
self._application.applicationShuttingDown.connect(self.stop)
# Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
self.addUSBOutputDeviceSignal.connect(self.addOutputDevice)
Application.getInstance().globalContainerStackChanged.connect(self.updateUSBPrinterOutputDevices)
self._application.globalContainerStackChanged.connect(self.updateUSBPrinterOutputDevices)
# The method updates/reset the USB settings for all connected USB devices
def updateUSBPrinterOutputDevices(self):
@ -69,7 +75,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
def _updateThread(self):
while self._check_updates:
container_stack = Application.getInstance().getGlobalContainerStack()
container_stack = self._application.getGlobalContainerStack()
if container_stack is None:
time.sleep(5)
continue
@ -81,19 +87,10 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._addRemovePorts(port_list)
time.sleep(5)
## Return the singleton instance of the USBPrinterManager
@classmethod
def getInstance(cls, engine = None, script_engine = None):
# Note: Explicit use of class name to prevent issues with inheritance.
if USBPrinterOutputDeviceManager._instance is None:
USBPrinterOutputDeviceManager._instance = cls()
return USBPrinterOutputDeviceManager._instance
@pyqtSlot(result = str)
def getDefaultFirmwareName(self):
# Check if there is a valid global container stack
global_container_stack = Application.getInstance().getGlobalContainerStack()
global_container_stack = self._application.getGlobalContainerStack()
if not global_container_stack:
Logger.log("e", "There is no global container stack. Can not update firmware.")
self._firmware_view.close()
@ -182,4 +179,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
return list(base_list)
_instance = None # type: "USBPrinterOutputDeviceManager"
__instance = None
@classmethod
def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager":
return cls.__instance

View file

@ -15,4 +15,4 @@ def register(app):
# We are violating the QT API here (as we use a factory, which is technically not allowed).
# but we don't really have another means for doing this (and it seems to you know -work-)
qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance()}
return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)}

View file

@ -1,28 +1,26 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Extension import Extension
from UM.Preferences import Preferences
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Logger import Logger
from cura.CuraApplication import CuraApplication
import os
from PyQt5.QtCore import QObject, pyqtSlot
import os.path
from UM.Extension import Extension
from UM.Logger import Logger
class UserAgreement(QObject, Extension):
def __init__(self):
def __init__(self, application):
super(UserAgreement, self).__init__()
self._application = application
self._user_agreement_window = None
self._user_agreement_context = None
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
Preferences.getInstance().addPreference("general/accepted_user_agreement", False)
self._application.engineCreatedSignal.connect(self._onEngineCreated)
self._application.getPreferences().addPreference("general/accepted_user_agreement", False)
def _onEngineCreated(self):
if not Preferences.getInstance().getValue("general/accepted_user_agreement"):
if not self._application.getPreferences().getValue("general/accepted_user_agreement"):
self.showUserAgreement()
def showUserAgreement(self):
@ -35,14 +33,14 @@ class UserAgreement(QObject, Extension):
def didAgree(self, user_choice):
if user_choice:
Logger.log("i", "User agreed to the user agreement")
Preferences.getInstance().setValue("general/accepted_user_agreement", True)
self._application.getPreferences().setValue("general/accepted_user_agreement", True)
self._user_agreement_window.hide()
else:
Logger.log("i", "User did NOT agree to the user agreement")
Preferences.getInstance().setValue("general/accepted_user_agreement", False)
CuraApplication.getInstance().quit()
CuraApplication.getInstance().setNeedToShowUserAgreement(False)
self._application.getPreferences().setValue("general/accepted_user_agreement", False)
self._application.quit()
self._application.setNeedToShowUserAgreement(False)
def createUserAgreementWindow(self):
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "UserAgreement.qml")
self._user_agreement_window = Application.getInstance().createQmlComponent(path, {"manager": self})
path = os.path.join(self._application.getPluginRegistry().getPluginPath(self.getPluginId()), "UserAgreement.qml")
self._user_agreement_window = self._application.createQmlComponent(path, {"manager": self})

View file

@ -7,4 +7,4 @@ def getMetaData():
return {}
def register(app):
return {"extension": UserAgreement.UserAgreement()}
return {"extension": UserAgreement.UserAgreement(app)}

View file

@ -107,7 +107,12 @@ class MachineInstance:
user_profile["values"] = {}
version_upgrade_manager = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance()
user_storage = os.path.join(Resources.getDataStoragePath(), next(iter(version_upgrade_manager.getStoragePaths("user"))))
user_version_to_paths_dict = version_upgrade_manager.getStoragePaths("user")
paths_set = set()
for paths in user_version_to_paths_dict.values():
paths_set |= paths
user_storage = os.path.join(Resources.getDataStoragePath(), next(iter(paths_set)))
user_profile_file = os.path.join(user_storage, urllib.parse.quote_plus(self._name) + "_current_settings.inst.cfg")
if not os.path.exists(user_storage):
os.makedirs(user_storage)

View file

@ -6,6 +6,9 @@ import io #To serialise the preference files afterwards.
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
_renamed_settings = {
"infill_hollow": "infill_support_enabled"
}
## Upgrades configurations from the state they were in at version 3.3 to the
# state they should be in at version 3.4.
@ -38,6 +41,17 @@ class VersionUpgrade33to34(VersionUpgrade):
# Update version number.
parser["general"]["version"] = "4"
if "values" in parser:
#If infill_hollow was enabled and the overhang angle was adjusted, copy that overhang angle to the new infill support angle.
if "infill_hollow" in parser["values"] and parser["values"]["infill_hollow"] and "support_angle" in parser["values"]:
parser["values"]["infill_support_angle"] = parser["values"]["support_angle"]
#Renamed settings.
for original, replacement in _renamed_settings.items():
if original in parser["values"]:
parser["values"][replacement] = parser["values"][original]
del parser["values"][original]
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]

View file

@ -21,7 +21,7 @@ def getMetaData():
},
"quality_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
"location": {"./quality_changes"}
},
"user": {
"get_version": upgrade.getCfgVersion,

View file

@ -38,14 +38,14 @@ class Shape:
self.name = name
class X3DReader(MeshReader):
def __init__(self):
super().__init__()
def __init__(self, application):
super().__init__(application)
self._supported_extensions = [".x3d"]
self._namespaces = {}
# Main entry point
# Reads the file, returns a SceneNode (possibly with nested ones), or None
def read(self, file_name):
def _read(self, file_name):
try:
self.defs = {}
self.shapes = []

View file

@ -16,4 +16,4 @@ def getMetaData():
}
def register(app):
return { "mesh_reader": X3DReader.X3DReader() }
return { "mesh_reader": X3DReader.X3DReader(app) }

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import copy
@ -12,10 +12,10 @@ import xml.etree.ElementTree as ET
from UM.Resources import Resources
from UM.Logger import Logger
from cura.CuraApplication import CuraApplication
import UM.Dictionary
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from .XmlMaterialValidator import XmlMaterialValidator
@ -540,7 +540,9 @@ class XmlMaterialProfile(InstanceContainer):
validation_message = XmlMaterialValidator.validateMaterialMetaData(meta_data)
if validation_message is not None:
raise Exception("Not valid material profile: %s" % (validation_message))
ConfigurationErrorMessage.getInstance().addFaultyContainers(self.getId())
Logger.log("e", "Not a valid material profile: {message}".format(message = validation_message))
return
property_values = {}
properties = data.iterfind("./um:properties/*", self.__namespaces)

View file

@ -6,7 +6,7 @@
"display_name": "3MF Reader",
"description": "Provides support for reading 3MF files.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -23,7 +23,7 @@
"display_name": "3MF Writer",
"description": "Provides support for writing 3MF files.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -40,7 +40,7 @@
"display_name": "Change Log",
"description": "Shows changes since latest checked version.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -57,7 +57,7 @@
"display_name": "CuraEngine Backend",
"description": "Provides the link to the CuraEngine slicing backend.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -74,7 +74,7 @@
"display_name": "Cura Profile Reader",
"description": "Provides support for importing Cura profiles.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -91,7 +91,7 @@
"display_name": "Cura Profile Writer",
"description": "Provides support for exporting Cura profiles.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -108,7 +108,7 @@
"display_name": "Firmware Update Checker",
"description": "Checks for firmware updates.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -125,7 +125,7 @@
"display_name": "Compressed G-code Reader",
"description": "Reads g-code from a compressed archive.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -142,7 +142,7 @@
"display_name": "Compressed G-code Writer",
"description": "Writes g-code to a compressed archive.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -159,7 +159,7 @@
"display_name": "G-Code Profile Reader",
"description": "Provides support for importing profiles from g-code files.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -176,7 +176,7 @@
"display_name": "G-Code Reader",
"description": "Allows loading and displaying G-code files.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "VictorLarchenko",
@ -193,7 +193,7 @@
"display_name": "G-Code Writer",
"description": "Writes g-code to a file.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -210,7 +210,7 @@
"display_name": "Image Reader",
"description": "Enables ability to generate printable geometry from 2D image files.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -227,7 +227,7 @@
"display_name": "Legacy Cura Profile Reader",
"description": "Provides support for importing profiles from legacy Cura versions.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -244,7 +244,7 @@
"display_name": "Machine Settings Action",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "fieldOfView",
@ -261,7 +261,7 @@
"display_name": "Model Checker",
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"package_version": "0.1.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -278,7 +278,7 @@
"display_name": "Monitor Stage",
"description": "Provides a monitor stage in Cura.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -295,7 +295,7 @@
"display_name": "Per-Object Settings Tool",
"description": "Provides the per-model settings.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -312,7 +312,7 @@
"display_name": "Post Processing",
"description": "Extension that allows for user created scripts for post processing.",
"package_version": "2.2.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -329,7 +329,7 @@
"display_name": "Prepare Stage",
"description": "Provides a prepare stage in Cura.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -346,7 +346,7 @@
"display_name": "Removable Drive Output Device",
"description": "Provides removable drive hotplugging and writing support.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -363,7 +363,7 @@
"display_name": "Simulation View",
"description": "Provides the Simulation view.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -380,7 +380,7 @@
"display_name": "Slice Info",
"description": "Submits anonymous slice info. Can be disabled through preferences.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -397,7 +397,7 @@
"display_name": "Solid View",
"description": "Provides a normal solid mesh view.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -414,7 +414,7 @@
"display_name": "Support Eraser Tool",
"description": "Creates an eraser mesh to block the printing of support in certain places.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -431,7 +431,7 @@
"display_name": "Toolbox",
"description": "Find, manage and install new Cura packages.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -448,7 +448,7 @@
"display_name": "UFP Writer",
"description": "Provides support for writing Ultimaker Format Packages.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -465,7 +465,7 @@
"display_name": "Ultimaker Machine Actions",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -482,7 +482,7 @@
"display_name": "UM3 Network Printing",
"description": "Manages network connections to Ultimaker 3 printers.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -499,7 +499,7 @@
"display_name": "USB Printing",
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -516,7 +516,7 @@
"display_name": "User Agreement",
"description": "Ask the user once if he/she agrees with our license.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -533,7 +533,7 @@
"display_name": "Version Upgrade 2.1 to 2.2",
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -550,7 +550,7 @@
"display_name": "Version Upgrade 2.2 to 2.4",
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -567,7 +567,7 @@
"display_name": "Version Upgrade 2.5 to 2.6",
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -584,7 +584,7 @@
"display_name": "Version Upgrade 2.6 to 2.7",
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -601,7 +601,7 @@
"display_name": "Version Upgrade 2.7 to 3.0",
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -618,7 +618,7 @@
"display_name": "Version Upgrade 3.0 to 3.1",
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -635,7 +635,7 @@
"display_name": "Version Upgrade 3.2 to 3.3",
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -652,7 +652,7 @@
"display_name": "Version Upgrade 3.3 to 3.4",
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -669,7 +669,7 @@
"display_name": "X3D Reader",
"description": "Provides support for reading X3D files.",
"package_version": "0.5.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "SevaAlekseyev",
@ -686,7 +686,7 @@
"display_name": "XML Material Profiles",
"description": "Provides capabilities to read and write XML-based material profiles.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -703,7 +703,7 @@
"display_name": "X-Ray View",
"description": "Provides the X-Ray view.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
@ -720,7 +720,7 @@
"display_name": "Dagoma Chromatik PLA",
"description": "Filament testé et approuvé pour les imprimantes 3D Dagoma. Chromatik est l'idéal pour débuter et suivre les tutoriels premiers pas. Il vous offre qualité et résistance pour chacune de vos impressions.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://dagoma.fr/boutique/filaments.html",
"author": {
"author_id": "Dagoma",
@ -737,7 +737,7 @@
"display_name": "FABtotum ABS",
"description": "This material is easy to be extruded but it is not the simplest to use. It is one of the most used in 3D printing to get very well finished objects. It is not sustainable and its smoke can be dangerous if inhaled. The reason to prefer this filament to PLA is mainly because of its precision and mechanical specs. ABS (for plastic) stands for Acrylonitrile Butadiene Styrene and it is a thermoplastic which is widely used in everyday objects. It can be printed with any FFF 3D printer which can get to high temperatures as it must be extruded in a range between 220° and 245°, so its compatible with all versions of the FABtotum Personal fabricator.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40",
"author": {
"author_id": "FABtotum",
@ -754,7 +754,7 @@
"display_name": "FABtotum Nylon",
"description": "When 3D printing started this material was not listed among the extrudable filaments. It is flexible as well as resistant to tractions. It is well known for its uses in textile but also in industries which require a strong and flexible material. There are different kinds of Nylon: 3D printing mostly uses Nylon 6 and Nylon 6.6, which are the most common. It requires higher temperatures to be printed, so a 3D printer must be able to reach them (around 240°C): the FABtotum, of course, can.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53",
"author": {
"author_id": "FABtotum",
@ -771,7 +771,7 @@
"display_name": "FABtotum PLA",
"description": "It is the most common filament used for 3D printing. It is studied to be bio-degradable as it comes from corn starchs sugar mainly. It is completely made of renewable sources and has no footprint on polluting. PLA stands for PolyLactic Acid and it is a thermoplastic that today is still considered the easiest material to be 3D printed. It can be extruded at lower temperatures: the standard range of FABtotums one is between 185° and 195°.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39",
"author": {
"author_id": "FABtotum",
@ -788,7 +788,7 @@
"display_name": "FABtotum TPU Shore 98A",
"description": "",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66",
"author": {
"author_id": "FABtotum",
@ -805,7 +805,7 @@
"display_name": "Fiberlogy HD PLA",
"description": "With our HD PLA you have many more options. You can use this material in two ways. Choose the one you like best. You can use it as a normal PLA and get prints characterized by a very good adhesion between the layers and high precision. You can also make your prints acquire similar properties to that of ABS better impact resistance and high temperature resistance. All you need is an oven. Yes, an oven! By annealing our HD PLA in an oven, in accordance with the manual, you will avoid all the inconveniences of printing with ABS, such as unpleasant odour or hazardous fumes.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
"author": {
"author_id": "Fiberlogy",
@ -822,7 +822,7 @@
"display_name": "Filo3D PLA",
"description": "Fast, safe and reliable printing. PLA is ideal for the fast and reliable printing of parts and prototypes with a great surface quality.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://dagoma.fr",
"author": {
"author_id": "Dagoma",
@ -839,7 +839,7 @@
"display_name": "IMADE3D JellyBOX PETG",
"description": "",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "http://shop.imade3d.com/filament.html",
"author": {
"author_id": "IMADE3D",
@ -856,7 +856,7 @@
"display_name": "IMADE3D JellyBOX PLA",
"description": "",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "http://shop.imade3d.com/filament.html",
"author": {
"author_id": "IMADE3D",
@ -873,7 +873,7 @@
"display_name": "Octofiber PLA",
"description": "PLA material from Octofiber.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://nl.octofiber.com/3d-printing-filament/pla.html",
"author": {
"author_id": "Octofiber",
@ -890,7 +890,7 @@
"display_name": "PolyFlex™ PLA",
"description": "PolyFlex™ is a highly flexible yet easy to print 3D printing material. Featuring good elasticity and a large strain-to- failure, PolyFlex™ opens up a completely new realm of applications.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "http://www.polymaker.com/shop/polyflex/",
"author": {
"author_id": "Polymaker",
@ -907,7 +907,7 @@
"display_name": "PolyMax™ PLA",
"description": "PolyMax™ PLA is a 3D printing material with excellent mechanical properties and printing quality. PolyMax™ PLA has an impact resistance of up to nine times that of regular PLA, and better overall mechanical properties than ABS.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "http://www.polymaker.com/shop/polymax/",
"author": {
"author_id": "Polymaker",
@ -924,7 +924,7 @@
"display_name": "PolyPlus™ PLA True Colour",
"description": "PolyPlus™ PLA is a premium PLA designed for all desktop FDM/FFF 3D printers. It is produced with our patented Jam-Free™ technology that ensures consistent extrusion and prevents jams.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "http://www.polymaker.com/shop/polyplus-true-colour/",
"author": {
"author_id": "Polymaker",
@ -941,7 +941,7 @@
"display_name": "PolyWood™ PLA",
"description": "PolyWood™ is a wood mimic printing material that contains no actual wood ensuring a clean Jam-Free™ printing experience.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "http://www.polymaker.com/shop/polywood/",
"author": {
"author_id": "Polymaker",
@ -958,7 +958,7 @@
"display_name": "Ultimaker ABS",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "Ultimaker",
@ -977,7 +977,7 @@
"display_name": "Ultimaker CPE",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "Ultimaker",
@ -996,7 +996,7 @@
"display_name": "Ultimaker Nylon",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "Ultimaker",
@ -1015,7 +1015,7 @@
"display_name": "Ultimaker PC",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com/products/materials/pc",
"author": {
"author_id": "Ultimaker",
@ -1034,7 +1034,7 @@
"display_name": "Ultimaker PLA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "Ultimaker",
@ -1053,7 +1053,7 @@
"display_name": "Ultimaker PVA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
"author_id": "Ultimaker",
@ -1072,7 +1072,7 @@
"display_name": "Vertex Delta ABS",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@ -1089,7 +1089,7 @@
"display_name": "Vertex Delta PET",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@ -1106,7 +1106,7 @@
"display_name": "Vertex Delta PLA",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@ -1123,7 +1123,7 @@
"display_name": "Vertex Delta TPU",
"description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0",
"cura_version": 4,
"sdk_version": 4,
"website": "https://vertex3dprinter.eu",
"author": {
"author_id": "Velleman",
@ -1132,5 +1132,277 @@
"website": "https://www.vellemanprojects.eu"
}
}
},
"ConsoleLogger": {
"package_info": {
"package_id": "ConsoleLogger",
"package_type": "plugin",
"display_name": "Console Logger",
"description": "Outputs log information to the console.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"OBJReader": {
"package_info": {
"package_id": "OBJReader",
"package_type": "plugin",
"display_name": "Wavefront OBJ Reader",
"description": "Makes it possible to read Wavefront OBJ files.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"OBJWriter": {
"package_info": {
"package_id": "OBJWriter",
"package_type": "plugin",
"display_name": "Wavefront OBJ Writer",
"description": "Makes it possible to write Wavefront OBJ files.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"STLReader": {
"package_info": {
"package_id": "STLReader",
"package_type": "plugin",
"display_name": "STL Reader",
"description": "Provides support for reading STL files.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"STLWriter": {
"package_info": {
"package_id": "STLWriter",
"package_type": "plugin",
"display_name": "STL Writer",
"description": "Provides support for writing STL files.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"FileLogger": {
"package_info": {
"package_id": "FileLogger",
"package_type": "plugin",
"display_name": "File Logger",
"description": "Outputs log information to a file in your settings folder.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"LocalContainerProvider": {
"package_info": {
"package_id": "LocalContainerProvider",
"package_type": "plugin",
"display_name": "Local Container Provider",
"description": "Provides built-in setting containers that come with the installation of the application.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"LocalFileOutputDevice": {
"package_info": {
"package_id": "LocalFileOutputDevice",
"package_type": "plugin",
"display_name": "Local File Output Device",
"description": "Enables saving to local files.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"CameraTool": {
"package_info": {
"package_id": "CameraTool",
"package_type": "plugin",
"display_name": "Camera Tool",
"description": "Provides the tool to manipulate the camera.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"MirrorTool": {
"package_info": {
"package_id": "MirrorTool",
"package_type": "plugin",
"display_name": "Mirror Tool",
"description": "Provides the Mirror tool.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"RotateTool": {
"package_info": {
"package_id": "RotateTool",
"package_type": "plugin",
"display_name": "Rotate Tool",
"description": "Provides the Rotate tool.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"ScaleTool": {
"package_info": {
"package_id": "ScaleTool",
"package_type": "plugin",
"display_name": "Scale Tool",
"description": "Provides the Scale tool.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"SelectionTool": {
"package_info": {
"package_id": "SelectionTool",
"package_type": "plugin",
"display_name": "Selection Tool",
"description": "Provides the Selection tool.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"TranslateTool": {
"package_info": {
"package_id": "TranslateTool",
"package_type": "plugin",
"display_name": "Move Tool",
"description": "Provides the Move tool.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"UpdateChecker": {
"package_info": {
"package_id": "UpdateChecker",
"package_type": "plugin",
"display_name": "Update Checker",
"description": "Checks for updates of the software.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"SimpleView": {
"package_info": {
"package_id": "SimpleView",
"package_type": "plugin",
"display_name": "Simple View",
"description": "Provides a simple solid mesh view.",
"package_version": "1.0.0",
"sdk_version": 4,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
}
}

View file

@ -1277,6 +1277,29 @@
}
}
},
"wall_min_flow":
{
"label": "Minimum Wall Flow",
"description": "Minimum allowed percentage flow for a wall line. The wall overlap compensation reduces a wall's flow when it lies close to an existing wall. Walls whose flow is less than this value will be replaced with a travel move. When using this setting, you must enable the wall overlap compensation and print the outer wall before inner walls.",
"unit": "%",
"minimum_value": "0",
"maximum_value": "100",
"default_value": 0,
"type": "float",
"enabled": "travel_compensate_overlapping_walls_0_enabled or travel_compensate_overlapping_walls_x_enabled",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"wall_min_flow_retract":
{
"label": "Prefer Retract",
"description": "If enabled, retraction is used rather than combing for travel moves that replace walls whose flow is below the minimum flow threshold.",
"type": "bool",
"default_value": false,
"enabled": "(travel_compensate_overlapping_walls_0_enabled or travel_compensate_overlapping_walls_x_enabled) and wall_min_flow > 0",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"fill_perimeter_gaps":
{
"label": "Fill Gaps Between Walls",
@ -1807,6 +1830,30 @@
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"infill_support_enabled":
{
"label": "Infill Support",
"description": "Print infill structures only where tops of the model should be supported. Enabling this reduces print time and material usage, but leads to ununiform object strength.",
"type": "bool",
"default_value": false,
"enabled": "infill_sparse_density > 0",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"infill_support_angle":
{
"label": "Infill Overhang Angle",
"description": "The minimum angle of internal overhangs for which infill is added. At a value of 0° objects are totally filled with infill, 90° will not provide any infill.",
"unit": "°",
"type": "float",
"minimum_value": "0",
"minimum_value_warning": "2",
"maximum_value": "90",
"default_value": 40,
"enabled": "infill_sparse_density > 0 and infill_support_enabled",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"skin_preshrink":
{
"label": "Skin Removal Width",
@ -3597,7 +3644,7 @@
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
"type": "extruder",
"default_value": "0",
"value": "-1",
"value": "defaultExtruderPosition()",
"enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false,
"settable_per_extruder": false,
@ -4338,7 +4385,7 @@
"description": "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion.",
"type": "extruder",
"default_value": "0",
"value": "-1",
"value": "defaultExtruderPosition()",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('adhesion_type') != 'none'",
"settable_per_mesh": false,
"settable_per_extruder": false
@ -5772,16 +5819,6 @@
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"cross_infill_apply_pockets_alternatingly":
{
"label": "Alternate Cross 3D Pockets",
"description": "Only apply pockets at half of the four-way crossings in the cross 3D pattern and alternate the location of the pockets between heights where the pattern is touching itself.",
"type": "bool",
"default_value": true,
"enabled": "infill_pattern == 'cross_3d'",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"cross_infill_density_image":
{
"label": "Cross Infill Density Image",
@ -5798,7 +5835,7 @@
"description": "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support.",
"type": "str",
"default_value": "",
"enabled": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
"enabled": "support_pattern == 'cross' or support_pattern == 'cross_3d'",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -5928,14 +5965,6 @@
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": true
},
"infill_hollow":
{
"label": "Hollow Out Objects",
"description": "Remove all infill and make the inside of the object eligible for support.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true
},
"magic_fuzzy_skin_enabled":
{
"label": "Fuzzy Skin",
@ -6672,14 +6701,6 @@
"type": "float",
"enabled": "bridge_settings_enabled and bridge_enable_more_layers",
"settable_per_mesh": true
},
"wall_try_line_thickness":
{
"label": "Try Multiple Line Thicknesses",
"description": "When creating inner walls, try various line thicknesses to fit the wall lines better in narrow spaces. This reduces or increases the inner wall line width by up to 0.01mm.",
"default_value": false,
"type": "bool",
"settable_per_mesh": true
}
}
},

View file

@ -7,6 +7,7 @@
"visible": true,
"author": "Ruben Dulek",
"manufacturer": "Malyan",
"machine_x3g_variant": "r1d",
"file_formats": "application/x3g"
},

View file

@ -122,7 +122,7 @@
"raft_jerk": { "value": "jerk_layer_0" },
"raft_margin": { "value": "10" },
"raft_surface_layers": { "value": "1" },
"retraction_amount": { "value": "2" },
"retraction_amount": { "value": "6.5" },
"retraction_count_max": { "value": "10" },
"retraction_extrusion_window": { "value": "1" },
"retraction_hop": { "value": "2" },

View file

@ -119,7 +119,7 @@
"raft_margin": { "value": "10" },
"raft_speed": { "value": "25" },
"raft_surface_layers": { "value": "1" },
"retraction_amount": { "value": "2" },
"retraction_amount": { "value": "6.5" },
"retraction_count_max": { "value": "10" },
"retraction_extrusion_window": { "value": "1" },
"retraction_hop": { "value": "2" },

View file

@ -323,10 +323,11 @@ UM.MainWindow
{
if (drop.urls.length > 0)
{
// As the drop area also supports plugins, first check if it's a plugin that was dropped.
if (drop.urls.length == 1)
var nonPackages = [];
for (var i = 0; i < drop.urls.length; i++)
{
var filename = drop.urls[0];
var filename = drop.urls[i];
if (filename.endsWith(".curapackage"))
{
// Try to install plugin & close.
@ -334,11 +335,13 @@ UM.MainWindow
packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting.");
packageInstallDialog.icon = StandardIcon.Information;
packageInstallDialog.open();
return;
}
else
{
nonPackages.push(filename);
}
}
openDialog.handleOpenFileUrls(drop.urls);
openDialog.handleOpenFileUrls(nonPackages);
}
}
}

View file

@ -80,14 +80,11 @@ Item {
property int unremovableSpacing: 5
text: PrintInformation.jobName
horizontalAlignment: TextInput.AlignRight
onTextChanged: {
PrintInformation.setJobName(text);
}
onEditingFinished: {
if (printJobTextfield.text != ''){
var new_name = text == "" ? "unnamed" : text;
PrintInformation.setJobName(new_name, true);
printJobTextfield.focus = false;
}
}
validator: RegExpValidator {
regExp: /^[^\\ \/ \*\?\|\[\]]*$/
}

View file

@ -92,6 +92,7 @@ Rectangle
anchors.verticalCenter: buildplateIcon.verticalCenter
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").height / 2)
text: configuration.buildplateConfiguration
renderType: Text.NativeRendering
color: textColor
}
}

View file

@ -26,6 +26,7 @@ Column
{
id: extruderLabel
text: catalog.i18nc("@label:extruder label", "Extruder")
renderType: Text.NativeRendering
elide: Text.ElideRight
anchors.left: parent.left
font: UM.Theme.getFont("default")
@ -59,6 +60,7 @@ Column
id: extruderNumberText
anchors.centerIn: parent
text: printCoreConfiguration.position + 1
renderType: Text.NativeRendering
font: UM.Theme.getFont("default")
color: mainColor
}
@ -69,6 +71,7 @@ Column
{
id: materialLabel
text: printCoreConfiguration.material.name
renderType: Text.NativeRendering
elide: Text.ElideRight
width: parent.width
font: UM.Theme.getFont("default_bold")
@ -79,6 +82,7 @@ Column
{
id: printCoreTypeLabel
text: printCoreConfiguration.hotendID
renderType: Text.NativeRendering
elide: Text.ElideRight
width: parent.width
font: UM.Theme.getFont("default")

View file

@ -13,7 +13,7 @@ Button
id: base
property var outputDevice: null
property var matched: updateOnSync()
text: matched == true ? "Yes" : "No"
text: matched == true ? catalog.i18nc("@label:extruder label", "Yes") : catalog.i18nc("@label:extruder label", "No")
width: parent.width
height: parent.height

View file

@ -63,8 +63,7 @@ Menu
exclusiveGroup: group
onTriggered:
{
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.MachineManager.setMaterial(activeExtruderIndex, model.container_node);
Cura.MachineManager.setMaterial(extruderIndex, model.container_node);
}
}
onObjectAdded: brandMaterialsMenu.insertItem(index, object)

View file

@ -404,10 +404,17 @@ TabView
id: spinBox
anchors.left: label.right
value: {
// In case the setting is not in the material...
if (!isNaN(parseFloat(materialPropertyProvider.properties.value)))
{
return parseFloat(materialPropertyProvider.properties.value);
}
// ... we search in the variant, and if it is not there...
if (!isNaN(parseFloat(variantPropertyProvider.properties.value)))
{
return parseFloat(variantPropertyProvider.properties.value);
}
// ... then look in the definition container.
if (!isNaN(parseFloat(machinePropertyProvider.properties.value)))
{
return parseFloat(machinePropertyProvider.properties.value);
@ -431,6 +438,13 @@ TabView
key: model.key
}
UM.ContainerPropertyProvider
{
id: variantPropertyProvider
containerId: Cura.MachineManager.activeVariantId
watchedProperties: [ "value" ]
key: model.key
}
UM.ContainerPropertyProvider
{
id: machinePropertyProvider
containerId: Cura.MachineManager.activeDefinitionId

View file

@ -93,14 +93,7 @@ SettingItem
{
target: control
property: "currentIndex"
value:
{
if(propertyProvider.properties.value == -1)
{
return control.getIndexByPosition(Cura.MachineManager.defaultExtruderPosition);
}
return propertyProvider.properties.value
}
value: control.getIndexByPosition(propertyProvider.properties.value)
// Sometimes when the value is already changed, the model is still being built.
// The when clause ensures that the current index is not updated when this happens.
when: control.model.items.length > 0

View file

@ -57,7 +57,8 @@ Item
interval: 50
running: false
repeat: false
onTriggered: {
onTriggered:
{
var item = Cura.QualityProfilesDropDownMenuModel.getItem(qualitySlider.value);
Cura.MachineManager.activeQualityGroup = item.quality_group;
}
@ -77,7 +78,8 @@ Item
{
// update needs to be called when the widgets are visible, otherwise the step width calculation
// will fail because the width of an invisible item is 0.
if (visible) {
if (visible)
{
qualityModel.update();
}
}
@ -97,24 +99,30 @@ Item
property var qualitySliderAvailableMax: 0
property var qualitySliderMarginRight: 0
function update () {
function update ()
{
reset()
var availableMin = -1
var availableMax = -1
for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++) {
for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++)
{
var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i)
// Add each quality item to the UI quality model
qualityModel.append(qualityItem)
// Set selected value
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type) {
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type)
{
// set to -1 when switching to user created profile so all ticks are clickable
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) {
if (Cura.SimpleModeSettingsManager.isProfileUserCreated)
{
qualityModel.qualitySliderActiveIndex = -1
} else {
}
else
{
qualityModel.qualitySliderActiveIndex = i
}
@ -122,18 +130,21 @@ Item
}
// Set min available
if (qualityItem.available && availableMin == -1) {
if (qualityItem.available && availableMin == -1)
{
availableMin = i
}
// Set max available
if (qualityItem.available) {
if (qualityItem.available)
{
availableMax = i
}
}
// Set total available ticks for active slider part
if (availableMin != -1) {
if (availableMin != -1)
{
qualityModel.availableTotalTicks = availableMax - availableMin + 1
}
@ -145,16 +156,23 @@ Item
qualityModel.qualitySliderAvailableMax = availableMax
}
function calculateSliderStepWidth (totalTicks) {
function calculateSliderStepWidth (totalTicks)
{
qualityModel.qualitySliderStepWidth = totalTicks != 0 ? Math.round((base.width * 0.55) / (totalTicks)) : 0
}
function calculateSliderMargins (availableMin, availableMax, totalTicks) {
if (availableMin == -1 || (availableMin == 0 && availableMax == 0)) {
function calculateSliderMargins (availableMin, availableMax, totalTicks)
{
if (availableMin == -1 || (availableMin == 0 && availableMax == 0))
{
qualityModel.qualitySliderMarginRight = Math.round(base.width * 0.55)
} else if (availableMin == availableMax) {
}
else if (availableMin == availableMax)
{
qualityModel.qualitySliderMarginRight = Math.round((totalTicks - availableMin) * qualitySliderStepWidth)
} else {
}
else
{
qualityModel.qualitySliderMarginRight = Math.round((totalTicks - availableMax) * qualitySliderStepWidth)
}
}
@ -215,16 +233,24 @@ Item
return result
}
x: {
x:
{
// Make sure the text aligns correctly with each tick
if (qualityModel.totalTicks == 0) {
if (qualityModel.totalTicks == 0)
{
// If there is only one tick, align it centrally
return Math.round(((base.width * 0.55) - width) / 2)
} else if (index == 0) {
}
else if (index == 0)
{
return Math.round(base.width * 0.55 / qualityModel.totalTicks) * index
} else if (index == qualityModel.totalTicks) {
}
else if (index == qualityModel.totalTicks)
{
return Math.round(base.width * 0.55 / qualityModel.totalTicks) * index - width
} else {
}
else
{
return Math.round((base.width * 0.55 / qualityModel.totalTicks) * index - (width / 2))
}
}
@ -291,7 +317,8 @@ Item
Rectangle
{
id: rightArea
width: {
width:
{
if(qualityModel.availableTotalTicks == 0)
return 0
@ -299,8 +326,10 @@ Item
}
height: parent.height
color: "transparent"
x: {
if (qualityModel.availableTotalTicks == 0) {
x:
{
if (qualityModel.availableTotalTicks == 0)
{
return 0
}
@ -310,7 +339,8 @@ Item
return totalGap
}
MouseArea {
MouseArea
{
anchors.fill: parent
hoverEnabled: true
enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false
@ -373,13 +403,16 @@ Item
style: SliderStyle
{
//Draw Available line
groove: Rectangle {
groove: Rectangle
{
implicitHeight: 2 * screenScaleFactor
color: UM.Theme.getColor("quality_slider_available")
radius: Math.round(height / 2)
}
handle: Item {
Rectangle {
handle: Item
{
Rectangle
{
id: qualityhandleButton
anchors.centerIn: parent
color: UM.Theme.getColor("quality_slider_available")
@ -391,11 +424,14 @@ Item
}
}
onValueChanged: {
onValueChanged:
{
// only change if an active machine is set and the slider is visible at all.
if (Cura.MachineManager.activeMachine != null && visible) {
if (Cura.MachineManager.activeMachine != null && visible)
{
// prevent updating during view initializing. Trigger only if the value changed by user
if (qualitySlider.value != qualityModel.qualitySliderActiveIndex && qualityModel.qualitySliderActiveIndex != -1) {
if (qualitySlider.value != qualityModel.qualitySliderActiveIndex && qualityModel.qualitySliderActiveIndex != -1)
{
// start updating with short delay
qualitySliderChangeTimer.start()
}
@ -587,8 +623,10 @@ Item
// same operation
var active_mode = UM.Preferences.getValue("cura/active_mode")
if (active_mode == 0 || active_mode == "simple") {
if (active_mode == 0 || active_mode == "simple")
{
Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue)
Cura.MachineManager.resetSettingForAllExtruders("infill_line_distance")
}
}

View file

@ -150,7 +150,20 @@ UM.Dialog
width: parent.width
Label
{
text: catalog.i18nc("@action:label", "Extruder %1").arg(modelData)
text: {
var extruder = Number(modelData)
var extruder_id = ""
if(!isNaN(extruder))
{
extruder_id = extruder + 1 // The extruder counter start from One and not Zero
}
else
{
extruder_id = modelData
}
return catalog.i18nc("@action:label", "Extruder %1").arg(extruder_id)
}
font.bold: true
}
Row

View file

@ -34,6 +34,7 @@ retraction_hop_enabled
[cooling]
cool_fan_enabled
cool_fan_speed
[support]
support_enable

View file

@ -87,6 +87,7 @@ gradual_infill_steps
gradual_infill_step_height
infill_before_walls
min_infill_area
infill_support_enabled
skin_preshrink
top_skin_preshrink
bottom_skin_preshrink
@ -369,7 +370,6 @@ spaghetti_infill_extra_volume
support_conical_enabled
support_conical_angle
support_conical_min_width
infill_hollow
magic_fuzzy_skin_enabled
magic_fuzzy_skin_thickness
magic_fuzzy_skin_point_density

View file

@ -532,7 +532,7 @@ QtObject {
SequentialAnimation on x {
id: xAnim
property int animEndPoint: Theme.getSize("message").width - (Theme.getSize("default_margin").width * 2) - Theme.getSize("progressbar_control").width
property int animEndPoint: Theme.getSize("message").width - Math.round((Theme.getSize("default_margin").width * 2.5)) - Theme.getSize("progressbar_control").width
running: control.indeterminate && control.visible
loops: Animation.Infinite
NumberAnimation { from: 0; to: xAnim.animEndPoint; duration: 2000;}