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_VERSION "master" CACHE STRING "Version name of Cura")
set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'") 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(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
configure_file(cura/CuraVersion.py.in CuraVersion.py @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.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Polygon import Polygon
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from cura.Arranging.ShapeArray import ShapeArray from cura.Arranging.ShapeArray import ShapeArray
from cura.Scene import ZOffsetDecorator from cura.Scene import ZOffsetDecorator
@ -45,7 +46,7 @@ class Arrange:
# \param scene_root Root for finding all scene nodes # \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed # \param fixed_nodes Scene nodes to be placed
@classmethod @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 = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst() arranger.centerFirst()
@ -58,9 +59,10 @@ class Arrange:
# Place all objects fixed nodes # Place all objects fixed nodes
for fixed_node in 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: if not vertices:
continue continue
vertices = vertices.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
points = copy.deepcopy(vertices._points) points = copy.deepcopy(vertices._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale) shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr) 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) ## Find placement for a node (using offset shape) and place it (using hull shape)
# return the nodes that should be placed # return the nodes that should be placed
# \param node # \param node
# \param offset_shape_arr ShapeArray with offset, used to find location # \param offset_shape_arr ShapeArray with offset, for placing the shape
# \param hull_shape_arr ShapeArray without 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): def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
new_node = copy.deepcopy(node) new_node = copy.deepcopy(node)
best_spot = self.bestSpot( 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 x, y = best_spot.x, best_spot.y
# Save the last priority. # Save the last priority.
@ -102,7 +104,7 @@ class Arrange:
if x is not None: # We could find a place if x is not None: # We could find a place
new_node.setPosition(Vector(x, center_y, y)) new_node.setPosition(Vector(x, center_y, y))
found_spot = True 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: else:
Logger.log("d", "Could not find spot!"), Logger.log("d", "Could not find spot!"),
found_spot = False found_spot = False

View file

@ -110,7 +110,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
arrange_array.add() arrange_array.add()
arranger = arrange_array.get(current_build_plate_number) 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 x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator) node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox(): if node.getBoundingBox():
@ -118,7 +118,7 @@ class ArrangeObjectsAllBuildPlatesJob(Job):
else: else:
center_y = 0 center_y = 0
if x is not None: # We could find a place 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) node.callDecoration("setBuildPlateNumber", current_build_plate_number)
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True)) 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_width = global_container_stack.getProperty("machine_width", "value")
machine_depth = global_container_stack.getProperty("machine_depth", "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 # Collect nodes to be placed
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr) nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
for node in self._nodes: for node in self._nodes:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset) 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)) 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. # Sort the nodes with the biggest area first.
@ -63,7 +66,7 @@ class ArrangeObjectsJob(Job):
start_priority = last_priority start_priority = last_priority
else: else:
start_priority = 0 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 x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator) node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox(): if node.getBoundingBox():
@ -74,7 +77,7 @@ class ArrangeObjectsJob(Job):
last_size = size last_size = size
last_priority = best_spot.priority 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)) grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
else: else:
Logger.log("d", "Arrange all: could not find spot!") Logger.log("d", "Arrange all: could not find spot!")

View file

@ -3,21 +3,20 @@
from PyQt5.QtCore import QTimer from PyQt5.QtCore import QTimer
from UM.Preferences import Preferences
from UM.Logger import Logger from UM.Logger import Logger
class AutoSave: class AutoSave:
def __init__(self, application): def __init__(self, application):
self._application = application self._application = application
Preferences.getInstance().preferenceChanged.connect(self._triggerTimer) self._application.getPreferences().preferenceChanged.connect(self._triggerTimer)
self._global_stack = None 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 = 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._change_timer.setSingleShot(True)
self._saving = False self._saving = False

View file

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

View file

@ -1,9 +1,18 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, QTimer import copy
from PyQt5.QtNetwork import QLocalServer import json
from PyQt5.QtNetwork import QLocalSocket 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.Qt.QtApplication import QtApplication
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
@ -74,6 +83,7 @@ from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager from cura.Machines.VariantManager import VariantManager
from .SingleInstance import SingleInstance
from .AutoSave import AutoSave from .AutoSave import AutoSave
from . import PlatformPhysics from . import PlatformPhysics
from . import BuildVolume from . import BuildVolume
@ -94,22 +104,10 @@ from cura.Settings.ContainerManager import ContainerManager
from cura.ObjectsModel import ObjectsModel from cura.ObjectsModel import ObjectsModel
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from UM.FlameProfiler import pyqtSlot 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 MYPY = False
if not MYPY: if not MYPY:
@ -144,20 +142,161 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes) 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() 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 # 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"]: for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants"]:
Resources.addExpectedDirNameInData(dir_name) 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"): if not hasattr(sys, "frozen"):
resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources") resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
Resources.addSearchPath(resource_path) Resources.addSearchPath(resource_path)
self._use_gui = True # Adds custom property types, settings types, and extra operators (functions) that need to be registered in
self._open_file_queue = [] # Files to open when plug-ins are loaded. # SettingDefinition and SettingFunction.
def __initializeSettingDefinitionsAndFunctions(self):
# Need to do this before ContainerRegistry tries to load the machines # 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_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
SettingDefinition.addSupportedProperty("settable_per_extruder", 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("extruderValues", ExtruderManager.getExtruderValues)
SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue) SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue)
SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue) 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.QualityInstanceContainer, "quality")
Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants") Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants")
@ -193,20 +334,64 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility") Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant") self._container_registry.addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material") self._container_registry.addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user") self._container_registry.addResourceType(self.ResourceTypes.UserInstanceContainer, "user")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train") self._container_registry.addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack, "machine") self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
## Initialise the version upgrade manager with Cura's storage paths. Resources.addType(self.ResourceTypes.QmlFiles, "qml")
# Needs to be here to prevent circular dependencies. Resources.addType(self.ResourceTypes.Firmware, "firmware")
import UM.VersionUpgradeManager
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", 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"), ("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._currently_loading_files = []
self._non_sliceable_extensions = [] self._non_sliceable_extensions = []
@ -254,6 +440,10 @@ class CuraApplication(QtApplication):
self._variant_manager = None self._variant_manager = None
self.default_theme = "cura-light" 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"))) self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@ -287,23 +477,6 @@ class CuraApplication(QtApplication):
"SelectionTool", "SelectionTool",
"TranslateTool", "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._i18n_catalog = i18nCatalog("cura")
self._update_platform_activity_timer = QTimer() self._update_platform_activity_timer = QTimer()
@ -316,56 +489,13 @@ class CuraApplication(QtApplication):
self.getController().contextMenuRequested.connect(self._onContextMenuRequested) self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed) 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...")) self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))
# Add empty variant, material and quality containers. with self._container_registry.lockFile():
# Since they are empty, they should never be serialized and instead just programmatically created. self._container_registry.loadAllMetadata()
# 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()
# set the setting version for Preferences # set the setting version for Preferences
preferences = Preferences.getInstance() preferences = self.getPreferences()
preferences.addPreference("metadata/setting_version", 0) 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. 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("view/filter_current_build_plate", False)
preferences.addPreference("cura/sidebar_collapsed", 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 [ for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
@ -410,13 +540,10 @@ class CuraApplication(QtApplication):
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize 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 CuraApplication.Created = True
def _onEngineCreated(self): def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@pyqtProperty(bool) @pyqtProperty(bool)
def needToShowUserAgreement(self): def needToShowUserAgreement(self):
@ -445,7 +572,7 @@ class CuraApplication(QtApplication):
## A reusable dialogbox ## 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 = []): def messageBox(self, title, text, informativeText = "", detailedText = "", buttons = QMessageBox.Ok, icon = QMessageBox.NoIcon, callback = None, callback_arguments = []):
self._message_box_callback = callback self._message_box_callback = callback
@ -456,14 +583,14 @@ class CuraApplication(QtApplication):
def discardOrKeepProfileChanges(self): def discardOrKeepProfileChanges(self):
has_user_interaction = False 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": if choice == "always_discard":
# don't show dialog and DISCARD the profile # don't show dialog and DISCARD the profile
self.discardOrKeepProfileChangesClosed("discard") self.discardOrKeepProfileChangesClosed("discard")
elif choice == "always_keep": elif choice == "always_keep":
# don't show dialog and KEEP the profile # don't show dialog and KEEP the profile
self.discardOrKeepProfileChangesClosed("keep") self.discardOrKeepProfileChangesClosed("keep")
elif self._use_gui: elif not self._is_headless:
# ALWAYS ask whether to keep or discard the profile # ALWAYS ask whether to keep or discard the profile
self.showDiscardOrKeepProfileChanges.emit() self.showDiscardOrKeepProfileChanges.emit()
has_user_interaction = True 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. # Do not do saving during application start or when data should not be safed on quit.
return return
ContainerRegistry.getInstance().saveDirtyContainers() ContainerRegistry.getInstance().saveDirtyContainers()
Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, self.savePreferences()
self._application_name + ".cfg"))
def saveStack(self, stack): def saveStack(self, stack):
ContainerRegistry.getInstance().saveContainer(stack) ContainerRegistry.getInstance().saveContainer(stack)
@pyqtSlot(str, result = QUrl) @pyqtSlot(str, result = QUrl)
def getDefaultPath(self, key): 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) return QUrl.fromLocalFile(default_path)
@pyqtSlot(str, str) @pyqtSlot(str, str)
def setDefaultPath(self, key, default_path): def setDefaultPath(self, key, default_path):
Preferences.getInstance().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile()) self.getPreferences().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile())
@classmethod
def getStaticVersion(cls):
return CuraVersion
## Handle loading of all plugin types (and the backend explicitly) ## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistry # \sa PluginRegistry
@ -545,127 +667,8 @@ class CuraApplication(QtApplication):
self._plugins_loaded = True 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): def run(self):
self.preRun() container_registry = self._container_registry
container_registry = ContainerRegistry.getInstance()
Logger.log("i", "Initializing variant manager") Logger.log("i", "Initializing variant manager")
self._variant_manager = VariantManager(container_registry) self._variant_manager = VariantManager(container_registry)
@ -684,29 +687,34 @@ class CuraApplication(QtApplication):
Logger.log("i", "Initializing machine manager") Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self) self._machine_manager = MachineManager(self)
Logger.log("i", "Initializing container manager")
self._container_manager = ContainerManager(self)
Logger.log("i", "Initializing machine error checker") Logger.log("i", "Initializing machine error checker")
self._machine_error_checker = MachineErrorChecker(self) self._machine_error_checker = MachineErrorChecker(self)
self._machine_error_checker.initialize() self._machine_error_checker.initialize()
# Check if we should run as single instance or not # Check if we should run as single instance or not. If so, set up a local socket server which listener which
self._setUpSingleInstanceServer() # coordinates multiple Cura instances and accepts commands.
if self._use_single_instance:
self.__setUpSingleInstanceServer()
# Setup scene and build volume # Setup scene and build volume
root = self.getController().getScene().getRoot() root = self.getController().getScene().getRoot()
self._volume = BuildVolume.BuildVolume(self.getController().getScene().getRoot()) self._volume = BuildVolume.BuildVolume(self, root)
Arrange.build_volume = self._volume Arrange.build_volume = self._volume
# initialize info objects # initialize info objects
self._print_information = PrintInformation.PrintInformation() self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self) self._cura_actions = CuraActions.CuraActions(self)
# Initialize setting visibility presets model # Initialize setting visibility presets model
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self) self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self)
default_visibility_profile = self._setting_visibility_presets_model.getItem(0) 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 # Detect in which mode to run and execute that mode
if self.getCommandLineOption("headless", False): if self._is_headless:
self.runWithoutGUI() self.runWithoutGUI()
else: else:
self.runWithGUI() self.runWithGUI()
@ -715,7 +723,6 @@ class CuraApplication(QtApplication):
self.initializationFinished.emit() self.initializationFinished.emit()
Logger.log("d", "Booting Cura took %s seconds", time.time() - self._boot_loading_time) 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 # 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 # initialized, for example opening files because they may show dialogs which can be closed due to incomplete
# GUI initialization. # GUI initialization.
@ -730,8 +737,12 @@ class CuraApplication(QtApplication):
self.exec_() self.exec_()
def __setUpSingleInstanceServer(self):
if self._use_single_instance:
self._single_instance.startServer()
def _onPostStart(self): def _onPostStart(self):
for file_name in self.getCommandLineOption("file", []): for file_name in self._files_to_open:
self.callLater(self._openFile, file_name) 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. 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) self.callLater(self._openFile, file_name)
@ -740,13 +751,10 @@ class CuraApplication(QtApplication):
## Run Cura without GUI elements and interaction (server mode). ## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self): def runWithoutGUI(self):
self._use_gui = False
self.closeSplash() self.closeSplash()
## Run Cura with GUI (desktop mode). ## Run Cura with GUI (desktop mode).
def runWithGUI(self): def runWithGUI(self):
self._use_gui = True
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene...")) self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
controller = self.getController() controller = self.getController()
@ -796,9 +804,6 @@ class CuraApplication(QtApplication):
# Hide the splash screen # Hide the splash screen
self.closeSplash() self.closeSplash()
def hasGui(self):
return self._use_gui
@pyqtSlot(result = QObject) @pyqtSlot(result = QObject)
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
return self._setting_visibility_presets_model return self._setting_visibility_presets_model
@ -813,7 +818,7 @@ class CuraApplication(QtApplication):
def getExtruderManager(self, *args): def getExtruderManager(self, *args):
if self._extruder_manager is None: if self._extruder_manager is None:
self._extruder_manager = ExtruderManager.createExtruderManager() self._extruder_manager = ExtruderManager()
return self._extruder_manager return self._extruder_manager
def getVariantManager(self, *args): def getVariantManager(self, *args):
@ -936,7 +941,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") 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. # 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"))) actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@ -970,7 +975,7 @@ class CuraApplication(QtApplication):
# Default # Default
self.getController().setActiveTool("TranslateTool") 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 self._center_after_select = True
else: else:
if self.getController().getActiveTool(): if self.getController().getActiveTool():
@ -1317,15 +1322,15 @@ class CuraApplication(QtApplication):
categories = list(set(categories)) categories = list(set(categories))
categories.sort() categories.sort()
joined = ";".join(categories) joined = ";".join(categories)
if joined != Preferences.getInstance().getValue("cura/categories_expanded"): if joined != self.getPreferences().getValue("cura/categories_expanded"):
Preferences.getInstance().setValue("cura/categories_expanded", joined) self.getPreferences().setValue("cura/categories_expanded", joined)
self.expandedCategoriesChanged.emit() self.expandedCategoriesChanged.emit()
expandedCategoriesChanged = pyqtSignal() expandedCategoriesChanged = pyqtSignal()
@pyqtProperty("QStringList", notify = expandedCategoriesChanged) @pyqtProperty("QStringList", notify = expandedCategoriesChanged)
def expandedCategories(self): def expandedCategories(self):
return Preferences.getInstance().getValue("cura/categories_expanded").split(";") return self.getPreferences().getValue("cura/categories_expanded").split(";")
@pyqtSlot() @pyqtSlot()
def mergeSelected(self): def mergeSelected(self):
@ -1476,8 +1481,7 @@ class CuraApplication(QtApplication):
# see GroupDecorator._onChildrenChanged # see GroupDecorator._onChildrenChanged
def _createSplashScreen(self): def _createSplashScreen(self):
run_headless = self.getCommandLineOption("headless", False) if self._is_headless:
if run_headless:
return None return None
return CuraSplashScreen.CuraSplashScreen() return CuraSplashScreen.CuraSplashScreen()
@ -1489,11 +1493,15 @@ class CuraApplication(QtApplication):
def _reloadMeshFinished(self, job): def _reloadMeshFinished(self, job):
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh! # 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() job_result = job.getResult()
if mesh_data: if len(job_result) == 0:
job._node.setMeshData(mesh_data) Logger.log("e", "Reloading the mesh failed.")
else: return
mesh_data = job_result[0].getMeshData()
if not mesh_data:
Logger.log("w", "Could not find a mesh in reloaded node.") Logger.log("w", "Could not find a mesh in reloaded node.")
return
job._node.setMeshData(mesh_data)
def _openFile(self, filename): def _openFile(self, filename):
self.readLocalFile(QUrl.fromLocalFile(filename)) self.readLocalFile(QUrl.fromLocalFile(filename))
@ -1557,10 +1565,11 @@ class CuraApplication(QtApplication):
f = file.toLocalFile() f = file.toLocalFile()
extension = os.path.splitext(f)[1] extension = os.path.splitext(f)[1]
extension = extension.lower()
filename = os.path.basename(f) filename = os.path.basename(f)
if len(self._currently_loading_files) > 0: 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 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( message = Message(
self._i18n_catalog.i18nc("@info:status", self._i18n_catalog.i18nc("@info:status",
"Only one G-code file can be loaded at a time. Skipped importing {0}", "Only one G-code file can be loaded at a time. Skipped importing {0}",
@ -1569,7 +1578,8 @@ class CuraApplication(QtApplication):
return return
# If file being loaded is non-slicable file, then prevent loading of any other files # 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] 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( message = Message(
self._i18n_catalog.i18nc("@info:status", self._i18n_catalog.i18nc("@info:status",
"Can't open any other file if G-code is loading. Skipped importing {0}", "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) self.fileLoaded.emit(filename)
arrange_objects_on_load = ( arrange_objects_on_load = (
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or not self.getPreferences().getValue("cura/use_multi_build_plate") or
not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load")) not self.getPreferences().getValue("cura/not_arrange_objects_on_load"))
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1 target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
root = self.getController().getScene().getRoot() root = self.getController().getScene().getRoot()

View file

@ -4,4 +4,6 @@
CuraVersion = "@CURA_VERSION@" CuraVersion = "@CURA_VERSION@"
CuraBuildType = "@CURA_BUILDTYPE@" CuraBuildType = "@CURA_BUILDTYPE@"
CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False 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. # 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 PyQt5.QtCore import QObject
from UM.FlameProfiler import pyqtSlot 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 ## Raised when trying to add an unknown machine action as a required action
class UnknownMachineActionError(Exception): class UnknownMachineActionError(Exception):
@ -20,23 +20,27 @@ class NotUniqueMachineActionError(Exception):
class MachineActionManager(QObject): class MachineActionManager(QObject):
def __init__(self, parent = None): def __init__(self, application, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = application
self._machine_actions = {} # Dict of all known machine actions self._machine_actions = {} # Dict of all known machine actions
self._required_actions = {} # Dict of all required actions by definition ID self._required_actions = {} # Dict of all required actions by definition ID
self._supported_actions = {} # Dict of all supported 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 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 # Add machine_action as plugin type
PluginRegistry.addType("machine_action", self.addMachineAction) PluginRegistry.addType("machine_action", self.addMachineAction)
# Ensure that all containers that were registered before creation of this registry are also handled. # 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. # 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) self._onContainerAdded(container)
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) container_registry.containerAdded.connect(self._onContainerAdded)
def _onContainerAdded(self, container): def _onContainerAdded(self, container):
## Ensure that the actions are added to this manager ## Ensure that the actions are added to this manager

View file

@ -291,9 +291,10 @@ class MaterialManager(QObject):
material_id_metadata_dict = dict() material_id_metadata_dict = dict()
for node in nodes_to_check: for node in nodes_to_check:
if node is not None: 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(): for material_id, node in node.material_map.items():
fallback_id = self.getFallbackMaterialIdByMaterialType(node.metadata["material"]) if material_id in machine_exclude_materials:
if fallback_id in machine_exclude_materials:
Logger.log("d", "Exclude material [%s] for machine [%s]", Logger.log("d", "Exclude material [%s] for machine [%s]",
material_id, machine_definition.getId()) material_id, machine_definition.getId())
continue continue

View file

@ -39,6 +39,8 @@ class BaseMaterialsModel(ListModel):
self._extruder_position = 0 self._extruder_position = 0
self._extruder_stack = None 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): def _updateExtruderStack(self):
global_stack = self._machine_manager.activeMachine global_stack = self._machine_manager.activeMachine
@ -50,9 +52,11 @@ class BaseMaterialsModel(ListModel):
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position)) self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None: if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update) self._extruder_stack.pyqtContainersChanged.connect(self._update)
# Force update the model when the extruder stack changes
self._update()
def setExtruderPosition(self, position: int): 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._extruder_position = position
self._updateExtruderStack() self._updateExtruderStack()
self.extruderPositionChanged.emit() self.extruderPositionChanged.emit()

View file

@ -8,9 +8,9 @@ from configparser import ConfigParser
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot
from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Preferences import Preferences
from UM.Resources import Resources from UM.Resources import Resources
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
@ -33,7 +33,7 @@ class SettingVisibilityPresetsModel(ListModel):
basic_item = self.items[1] basic_item = self.items[1]
basic_visibile_settings = ";".join(basic_item["settings"]) basic_visibile_settings = ";".join(basic_item["settings"])
self._preferences = Preferences.getInstance() self._preferences = Application.getInstance().getPreferences()
# Preference to store which preset is currently selected # Preference to store which preset is currently selected
self._preferences.addPreference("cura/active_setting_visibility_preset", "basic") 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) # 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. # Cura is released under the terms of the LGPLv3 or higher.
import copy
from UM.Job import Job from UM.Job import Job
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Message import Message from UM.Message import Message
@ -36,7 +38,7 @@ class MultiplyObjectsJob(Job):
root = scene.getRoot() root = scene.getRoot()
scale = 0.5 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 = [] processed_nodes = []
nodes = [] nodes = []
@ -64,6 +66,8 @@ class MultiplyObjectsJob(Job):
# We do place the nodes one by one, as we want to yield in between. # We do place the nodes one by one, as we want to yield in between.
if not node_too_big: if not node_too_big:
new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) 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: if node_too_big or not solution_found:
found_solution_for_all = False found_solution_for_all = False
new_location = new_node.getPosition() 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.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Preferences import Preferences
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
@ -20,7 +19,7 @@ class ObjectsModel(ListModel):
super().__init__() super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed) 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 = QTimer()
self._update_timer.setInterval(100) self._update_timer.setInterval(100)
@ -38,7 +37,7 @@ class ObjectsModel(ListModel):
def _update(self, *args): def _update(self, *args):
nodes = [] 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 active_build_plate_number = self._build_plate_number
group_nr = 1 group_nr = 1
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): 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.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Preferences import Preferences
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator 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._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 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) Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", False)
Preferences.getInstance().addPreference("physics/automatic_drop_down", True) Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True)
def _onSceneChanged(self, source): def _onSceneChanged(self, source):
if not source.getMeshData(): if not source.getMeshData():
@ -71,7 +70,7 @@ class PlatformPhysics:
# Move it downwards if bottom is above platform # Move it downwards if bottom is above platform
move_vector = Vector() 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 z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
move_vector = move_vector.set(y = -bbox.bottom + z_offset) move_vector = move_vector.set(y = -bbox.bottom + z_offset)
@ -80,7 +79,7 @@ class PlatformPhysics:
node.addDecorator(ConvexHullDecorator()) node.addDecorator(ConvexHullDecorator())
# only push away objects if this node is a printing mesh # 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 # Check for collisions between convex hulls
for other_node in BreadthFirstIterator(root): for other_node in BreadthFirstIterator(root):
# Ignore root, ourselves and anything that is not a normal SceneNode. # Ignore root, ourselves and anything that is not a normal SceneNode.

View file

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

View file

@ -24,7 +24,7 @@ class ConvexHullNode(SceneNode):
self._original_parent = parent self._original_parent = parent
# Color of the drawn convex hull # 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()) self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
else: else:
self._color = Color(0, 0, 0) self._color = Color(0, 0, 0)

View file

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

View file

@ -1,32 +1,25 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import os.path import os
import urllib.parse import urllib.parse
import uuid import uuid
from typing import Dict, Union from typing import Dict, Union
from PyQt5.QtCore import QObject, QUrl, QVariant from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from UM.PluginRegistry import PluginRegistry from UM.i18n import i18nCatalog
from UM.SaveFile import SaveFile from UM.FlameProfiler import pyqtSlot
from UM.Platform import Platform
from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.Logger import Logger 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.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer 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") 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 # 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. # when a certain action happens. This can be done through this class.
class ContainerManager(QObject): class ContainerManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._application = Application.getInstance() def __init__(self, application):
self._container_registry = ContainerRegistry.getInstance() 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._machine_manager = self._application.getMachineManager()
self._material_manager = self._application.getMaterialManager() self._material_manager = self._application.getMaterialManager()
self._container_name_filters = {} self._container_name_filters = {}
@ -129,7 +128,7 @@ class ContainerManager(QObject):
container.setProperty(setting_key, property_name, property_value) container.setProperty(setting_key, property_name, property_value)
basefile = container.getMetaDataEntry("base_file", container_id) 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: if sibbling_container != container:
sibbling_container.setProperty(setting_key, property_name, property_value) 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. # \return \type{bool} True if successful, False if not.
@pyqtSlot(result = bool) @pyqtSlot(result = bool)
def updateQualityChanges(self): def updateQualityChanges(self):
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = self._machine_manager.activeMachine
if not global_stack: if not global_stack:
return False return False
self._machine_manager.blurSettings.emit() 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. # Find the quality_changes container for this stack and merge the contents of the top container into it.
quality_changes = stack.qualityChanges quality_changes = stack.qualityChanges
if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()): if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
@ -334,13 +335,15 @@ class ContainerManager(QObject):
send_emits_containers = [] send_emits_containers = []
# Go through global and extruder stacks and clear their topmost container (the user settings). # 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 = stack.userChanges
container.clear() container.clear()
send_emits_containers.append(container) send_emits_containers.append(container)
# user changes are possibly added to make the current setup match the current enabled extruders # 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: for container in send_emits_containers:
container.sendPostponedEmits() container.sendPostponedEmits()
@ -381,21 +384,6 @@ class ContainerManager(QObject):
if container is not None: if container is not None:
container.setMetaDataEntry("GUID", new_guid) 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): def _performMerge(self, merge_into, merge, clear_settings = True):
if merge == merge_into: if merge == merge_into:
return return
@ -415,7 +403,7 @@ class ContainerManager(QObject):
serialize_type = "" serialize_type = ""
try: try:
plugin_metadata = PluginRegistry.getInstance().getMetaData(plugin_id) plugin_metadata = self._plugin_registry.getMetaData(plugin_id)
if plugin_metadata: if plugin_metadata:
serialize_type = plugin_metadata["settings_container"]["type"] serialize_type = plugin_metadata["settings_container"]["type"]
else: 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] 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) 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] extruder_definition = extruder_definitions[0]
unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id 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.setName(extruder_definition.getName())
extruder_stack.setDefinition(extruder_definition) extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) 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 # This also means that operations on the stack that modifies the container ordering is prohibited and
# will raise an exception. # will raise an exception.
class CuraContainerStack(ContainerStack): class CuraContainerStack(ContainerStack):
def __init__(self, container_id: str, *args, **kwargs): def __init__(self, container_id: str):
super().__init__(container_id, *args, **kwargs) super().__init__(container_id)
self._container_registry = ContainerRegistry.getInstance() self._container_registry = ContainerRegistry.getInstance()

View file

@ -7,7 +7,6 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Machines.VariantManager import VariantType from cura.Machines.VariantManager import VariantType
from .GlobalStack import GlobalStack from .GlobalStack import GlobalStack
@ -29,7 +28,7 @@ class CuraStackBuilder:
variant_manager = application.getVariantManager() variant_manager = application.getVariantManager()
material_manager = application.getMaterialManager() material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager() quality_manager = application.getQualityManager()
registry = ContainerRegistry.getInstance() registry = application.getContainerRegistry()
definitions = registry.findDefinitionContainers(id = definition_id) definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions: if not definitions:
@ -99,8 +98,7 @@ class CuraStackBuilder:
position = position, position = position,
variant_container = extruder_variant_container, variant_container = extruder_variant_container,
material_container = material_container, material_container = material_container,
quality_container = application.empty_quality_container, quality_container = application.empty_quality_container
global_stack = new_global_stack,
) )
new_extruder.setNextStack(new_global_stack) new_extruder.setNextStack(new_global_stack)
new_global_stack.addExtruder(new_extruder) new_global_stack.addExtruder(new_extruder)
@ -139,11 +137,12 @@ class CuraStackBuilder:
@classmethod @classmethod
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str, def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
position: int, position: int,
variant_container, material_container, quality_container, global_stack) -> ExtruderStack: variant_container, material_container, quality_container) -> ExtruderStack:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance() 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.setName(extruder_definition.getName())
stack.setDefinition(extruder_definition) 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 # 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 # properties. This makes the create operation more transactional, since any problems
# setting properties will not result in incomplete containers being added. # setting properties will not result in incomplete containers being added.
ContainerRegistry.getInstance().addContainer(user_container) registry.addContainer(user_container)
return stack return stack
@ -178,6 +177,7 @@ class CuraStackBuilder:
variant_container, material_container, quality_container) -> GlobalStack: variant_container, material_container, quality_container) -> GlobalStack:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
stack = GlobalStack(new_stack_id) stack = GlobalStack(new_stack_id)
stack.setDefinition(definition) stack.setDefinition(definition)
@ -193,7 +193,7 @@ class CuraStackBuilder:
stack.qualityChanges = application.empty_quality_changes_container stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container stack.userChanges = user_container
ContainerRegistry.getInstance().addContainer(user_container) registry.addContainer(user_container)
return stack return stack
@ -201,8 +201,10 @@ class CuraStackBuilder:
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str, def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
is_global_stack: bool) -> "InstanceContainer": is_global_stack: bool) -> "InstanceContainer":
from cura.CuraApplication import CuraApplication 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 = InstanceContainer(unique_container_name)
container.setDefinition(definition_id) container.setDefinition(definition_id)
@ -217,15 +219,17 @@ class CuraStackBuilder:
@classmethod @classmethod
def createDefinitionChangesContainer(cls, container_stack, container_name): def createDefinitionChangesContainer(cls, container_stack, container_name):
from cura.CuraApplication import CuraApplication 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 = InstanceContainer(unique_container_name)
definition_changes_container.setDefinition(container_stack.getBottom().getId()) definition_changes_container.setDefinition(container_stack.getBottom().getId())
definition_changes_container.addMetaDataEntry("type", "definition_changes") definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) 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 container_stack.definitionChanges = definition_changes_container
return 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.SettingInstance import SettingInstance
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from typing import Optional, List, TYPE_CHECKING, Union from typing import Optional, List, TYPE_CHECKING, Union
if TYPE_CHECKING: if TYPE_CHECKING:
@ -29,6 +30,10 @@ class ExtruderManager(QObject):
## Registers listeners and such to listen to changes to the extruders. ## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None): 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) super().__init__(parent)
self._application = Application.getInstance() self._application = Application.getInstance()
@ -92,28 +97,6 @@ class ExtruderManager(QObject):
if extruder.getId() == extruder_stack_id: if extruder.getId() == extruder_stack_id:
return extruder.qualityChanges.getId() 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. ## Changes the active extruder by index.
# #
# \param index The index of the new active extruder. # \param index The index of the new active extruder.
@ -557,6 +540,11 @@ class ExtruderManager(QObject):
return result 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. ## Get all extruder values for a certain setting.
# #
# This is exposed to qml for display purposes # This is exposed to qml for display purposes
@ -747,3 +735,9 @@ class ExtruderManager(QObject):
resolved_value = global_stack.getProperty(key, "value", context = context) resolved_value = global_stack.getProperty(key, "value", context = context)
return resolved_value 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): class ExtruderStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs): def __init__(self, container_id: str):
super().__init__(container_id, *args, **kwargs) super().__init__(container_id)
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility 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. ## Represents the Global or Machine stack and its related containers.
# #
class GlobalStack(CuraContainerStack): class GlobalStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs): def __init__(self, container_id: str):
super().__init__(container_id, *args, **kwargs) super().__init__(container_id)
self.addMetaDataEntry("type", "machine") # For backward compatibility self.addMetaDataEntry("type", "machine") # For backward compatibility

View file

@ -17,7 +17,6 @@ from UM.FlameProfiler import pyqtSlot
from UM import Util from UM import Util
from UM.Application import Application from UM.Application import Application
from UM.Preferences import Preferences
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
@ -98,12 +97,12 @@ class MachineManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged) ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
self.activeStackChanged.connect(self.activeStackValueChanged) self.activeStackChanged.connect(self.activeStackValueChanged)
Preferences.getInstance().addPreference("cura/active_machine", "") self._application.getPreferences().addPreference("cura/active_machine", "")
self._global_event_keys = set() self._global_event_keys = set()
self._printer_output_devices = [] # type: List[PrinterOutputDevice] 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 # There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged() self._onOutputDevicesChanged()
@ -164,14 +163,14 @@ class MachineManager(QObject):
rootMaterialChanged = pyqtSignal() rootMaterialChanged = pyqtSignal()
def setInitialActiveMachine(self) -> None: 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): if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
# An active machine was saved, so restore it. # An active machine was saved, so restore it.
self.setActiveMachine(active_machine_id) self.setActiveMachine(active_machine_id)
def _onOutputDevicesChanged(self) -> None: def _onOutputDevicesChanged(self) -> None:
self._printer_output_devices = [] 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): if isinstance(printer_output_device, PrinterOutputDevice):
self._printer_output_devices.append(printer_output_device) self._printer_output_devices.append(printer_output_device)
@ -238,7 +237,7 @@ class MachineManager(QObject):
extruder_stack.containersChanged.disconnect(self._onContainersChanged) extruder_stack.containersChanged.disconnect(self._onContainersChanged)
# Update the local global container stack reference # 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: if self._global_container_stack:
self.updateDefaultExtruder() self.updateDefaultExtruder()
self.updateNumberExtrudersEnabled() 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 # after switching the global stack we reconnect all the signals and set the variant and material references
if self._global_container_stack: 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.nameChanged.connect(self._onMachineNameChanged)
self._global_container_stack.containersChanged.connect(self._onContainersChanged) 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: 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()]: 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()] del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
self.activeQualityGroupChanged.emit() self.activeQualityGroupChanged.emit()
@ -364,7 +363,7 @@ class MachineManager(QObject):
return # We're done here return # We're done here
ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder
self._global_container_stack = global_stack self._global_container_stack = global_stack
Application.getInstance().setGlobalContainerStack(global_stack) self._application.setGlobalContainerStack(global_stack)
ExtruderManager.getInstance()._globalContainerStackChanged() ExtruderManager.getInstance()._globalContainerStackChanged()
self._initMachineState(containers[0]) self._initMachineState(containers[0])
self._onGlobalContainerChanged() self._onGlobalContainerChanged()
@ -655,6 +654,15 @@ class MachineManager(QObject):
return "" 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) @pyqtProperty(str, notify = activeVariantChanged)
def activeVariantBuildplateName(self) -> str: def activeVariantBuildplateName(self) -> str:
if self._global_container_stack: if self._global_container_stack:
@ -838,7 +846,7 @@ class MachineManager(QObject):
## Set the amount of extruders on the active machine (global stack) ## Set the amount of extruders on the active machine (global stack)
# \param extruder_count int the number of extruders to set # \param extruder_count int the number of extruders to set
def setActiveMachineExtruderCount(self, extruder_count: int) -> None: 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 definition_changes_container = self._global_container_stack.definitionChanges
if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container: if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container:
@ -855,7 +863,7 @@ class MachineManager(QObject):
self.correctExtruderSettings() self.correctExtruderSettings()
# Check to see if any objects are set to print with an extruder that will no longer exist # 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): for node in DepthFirstIterator(root_node):
if node.getMeshData(): if node.getMeshData():
extruder_nr = node.callDecoration("getActiveExtruderPosition") extruder_nr = node.callDecoration("getActiveExtruderPosition")
@ -888,7 +896,7 @@ class MachineManager(QObject):
global_user_container.removeInstance(setting_key) global_user_container.removeInstance(setting_key)
# Signal that the global stack has changed # Signal that the global stack has changed
Application.getInstance().globalContainerStackChanged.emit() self._application.globalContainerStackChanged.emit()
self.forceUpdateAllSettings() self.forceUpdateAllSettings()
@pyqtSlot(int, result = QObject) @pyqtSlot(int, result = QObject)
@ -982,6 +990,14 @@ class MachineManager(QObject):
container = extruder.userChanges container = extruder.userChanges
container.setProperty(setting_name, property_name, property_value) 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) @pyqtProperty("QVariantList", notify = globalContainerChanged)
def currentExtruderPositions(self) -> List[str]: def currentExtruderPositions(self) -> List[str]:
if self._global_container_stack is None: if self._global_container_stack is None:
@ -1032,6 +1048,10 @@ class MachineManager(QObject):
self.activeQualityChangesGroupChanged.emit() self.activeQualityChangesGroupChanged.emit()
def _setQualityGroup(self, quality_group, empty_quality_changes: bool = True) -> None: 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: if quality_group.node_for_global.getContainer() is None:
return return
for node in quality_group.nodes_for_extruders.values(): for node in quality_group.nodes_for_extruders.values():
@ -1042,10 +1062,6 @@ class MachineManager(QObject):
if empty_quality_changes: if empty_quality_changes:
self._current_quality_changes_group = None self._current_quality_changes_group = None
if quality_group is None:
self._setEmptyQuality()
return
# Set quality and quality_changes for the GlobalStack # Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer() self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes: if empty_quality_changes:
@ -1120,7 +1136,7 @@ class MachineManager(QObject):
def _setGlobalVariant(self, container_node): def _setGlobalVariant(self, container_node):
self._global_container_stack.variant = container_node.getContainer() self._global_container_stack.variant = container_node.getContainer()
if not self._global_container_stack.variant: 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): def _setMaterial(self, position, container_node = None):
if container_node and container_node.getContainer(): if container_node and container_node.getContainer():
@ -1280,6 +1296,10 @@ class MachineManager(QObject):
self._global_container_stack.variant = self._empty_variant_container self._global_container_stack.variant = self._empty_variant_container
self._updateQualityWithMaterial() 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' ## 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: def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine") machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
@ -1331,6 +1351,10 @@ class MachineManager(QObject):
self._setMaterial(position, container_node) self._setMaterial(position, container_node)
self._updateQualityWithMaterial() 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) @pyqtSlot(str, str)
def setVariantByName(self, position: str, variant_name: str) -> None: def setVariantByName(self, position: str, variant_name: str) -> None:
machine_definition_id = self._global_container_stack.definition.id machine_definition_id = self._global_container_stack.definition.id
@ -1346,6 +1370,10 @@ class MachineManager(QObject):
self._updateMaterialWithVariant(position) self._updateMaterialWithVariant(position)
self._updateQualityWithMaterial() 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) @pyqtSlot(str)
def setQualityGroupByQualityType(self, quality_type: str) -> None: def setQualityGroupByQualityType(self, quality_type: str) -> None:
if self._global_container_stack is None: if self._global_container_stack is None:
@ -1362,7 +1390,7 @@ class MachineManager(QObject):
self._setQualityGroup(quality_group) self._setQualityGroup(quality_group)
# See if we need to show the Discard or Keep changes screen # 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() self._application.discardOrKeepProfileChanges()
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
@ -1376,7 +1404,7 @@ class MachineManager(QObject):
self._setQualityChangesGroup(quality_changes_group) self._setQualityChangesGroup(quality_changes_group)
# See if we need to show the Discard or Keep changes screen # 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() self._application.discardOrKeepProfileChanges()
@pyqtSlot() @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 #!/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. # Cura is released under the terms of the LGPLv3 or higher.
import argparse import argparse
import faulthandler
import os import os
import sys import sys
@ -27,7 +28,7 @@ known_args = vars(parser.parse_known_args()[0])
if not known_args["debug"]: if not known_args["debug"]:
def get_cura_dir_path(): def get_cura_dir_path():
if Platform.isWindows(): if Platform.isWindows():
return os.path.expanduser("~/AppData/Roaming/cura/") return os.path.expanduser("~/AppData/Roaming/cura")
elif Platform.isLinux(): elif Platform.isLinux():
return os.path.expanduser("~/.local/share/cura") return os.path.expanduser("~/.local/share/cura")
elif Platform.isOSX(): 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.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") 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 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 # 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. # The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
try: try:
import ctypes import ctypes
@ -79,6 +77,7 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u
sys.path.remove(PATH_real) sys.path.remove(PATH_real)
sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0. sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0.
def exceptHook(hook_type, value, traceback): def exceptHook(hook_type, value, traceback):
from cura.CrashHandler import CrashHandler from cura.CrashHandler import CrashHandler
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
@ -121,25 +120,25 @@ def exceptHook(hook_type, value, traceback):
_crash_handler.early_crash_dialog.show() _crash_handler.early_crash_dialog.show()
sys.exit(application.exec_()) 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 # Workaround for a race condition on certain systems where there
# is a race condition between Arcus and PyQt. Importing Arcus # is a race condition between Arcus and PyQt. Importing Arcus
# first seems to prevent Sip from going into a state where it # first seems to prevent Sip from going into a state where it
# tries to create PyQt objects on a non-main thread. # tries to create PyQt objects on a non-main thread.
import Arcus #@UnusedImport import Arcus #@UnusedImport
import cura.CuraApplication from cura.CuraApplication import CuraApplication
import cura.Settings.CuraContainerRegistry
faulthandler.enable() app = CuraApplication()
app.addCommandLineOptions()
app.parseCliOptions()
app.initialize()
# Force an instance of CuraContainerRegistry to be created and reused later. app.startSplashWindowPhase()
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance() 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() app.run()

View file

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

View file

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

View file

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

View file

@ -51,7 +51,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
self._writeContainerToArchive(container, archive) self._writeContainerToArchive(container, archive)
# Write preferences to 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() temp_preferences = Preferences()
for preference in {"general/visible_settings", "cura/active_mode", "cura/categories_expanded"}: for preference in {"general/visible_settings", "cura/active_mode", "cura/categories_expanded"}:
temp_preferences.addPreference(preference, None) temp_preferences.addPreference(preference, None)

View file

@ -3,7 +3,6 @@
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Extension import Extension from UM.Extension import Extension
from UM.Preferences import Preferences
from UM.Application import Application from UM.Application import Application
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Version import Version from UM.Version import Version
@ -29,7 +28,7 @@ class ChangeLog(Extension, QObject,):
self._change_logs = None self._change_logs = None
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) 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) self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
def getChangeLogs(self): def getChangeLogs(self):
@ -79,12 +78,12 @@ class ChangeLog(Extension, QObject,):
if not self._current_app_version: if not self._current_app_version:
return #We're on dev branch. 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") latest_version_shown = Version("0.0.0")
else: 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 # Do not show the changelog when there is no global container stack
# This implies we are running Cura for the first time. # 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] [3.3.0]
*Profile for the Ultimaker S5 *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 *Real bridging - smartavionics
New experimental feature that detects bridges, adjusting the print speed, slow and fan speed to enhance print quality on bridging parts. 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. 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 *Use RapidJSON and ClipperLib from system libraries

View file

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

View file

@ -6,7 +6,6 @@ import gc
from UM.Job import Job from UM.Job import Job
from UM.Application import Application from UM.Application import Application
from UM.Mesh.MeshData import MeshData from UM.Mesh.MeshData import MeshData
from UM.Preferences import Preferences
from UM.View.GL.OpenGLContext import OpenGLContext from UM.View.GL.OpenGLContext import OpenGLContext
from UM.Message import Message from UM.Message import Message
@ -199,7 +198,7 @@ class ProcessSlicedLayersJob(Job):
material_color_map[0, :] = color material_color_map[0, :] = color
# We have to scale the colors for compatibility mode # 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 line_type_brightness = 0.5 # for compatibility mode
else: else:
line_type_brightness = 1.0 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 # \return A dictionary of replacement tokens to the values they should be
# replaced with. # replaced with.
def _buildReplacementTokens(self, stack) -> dict: def _buildReplacementTokens(self, stack) -> dict:
default_extruder_position = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
result = {} result = {}
for key in stack.getAllKeys(): for key in stack.getAllKeys():
setting_type = stack.definition.getProperty(key, "type")
value = stack.getProperty(key, "value") value = stack.getProperty(key, "value")
if setting_type == "extruder" and value == -1:
# replace with the default value
value = default_extruder_position
result[key] = value result[key] = value
Job.yieldThread() Job.yieldThread()

View file

@ -5,8 +5,7 @@ from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from UM.Extension import Extension from UM.Extension import Extension
from UM.Preferences import Preferences from UM.Application import Application
from UM.Logger import Logger
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from cura.Settings.GlobalStack import GlobalStack 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 # 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 # 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 # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the
# 'check for updates' option # 'check for updates' option
Preferences.getInstance().addPreference("info/automatic_update_check", True) Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True)
if Preferences.getInstance().getValue("info/automatic_update_check"): if Application.getInstance().getPreferences().getValue("info/automatic_update_check"):
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
self._download_url = None self._download_url = None

View file

@ -1,7 +1,6 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Preferences import Preferences
from UM.Application import Application from UM.Application import Application
from UM.Message import Message from UM.Message import Message
from UM.Logger import Logger from UM.Logger import Logger
@ -51,11 +50,11 @@ class FirmwareUpdateCheckerJob(Job):
current_version = reader(current_version_file).readline().rstrip() current_version = reader(current_version_file).readline().rstrip()
# If it is the first time the version is checked, the checked_version is '' # 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 # 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 # 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) 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, # 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. # notify the user when no new firmware version is available.
if (checked_version != "") and (checked_version != current_version): if (checked_version != "") and (checked_version != current_version):
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") 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 = Message(i18n_catalog.i18nc(
message.addAction("download", i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]") "@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 we do this in a cool way, the download url should be available in the JSON file
if self._set_download_url_callback: 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.actionTriggered.connect(self._callback)
message.show() 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! # If you're zipping g-code, you might as well use gzip!
class GCodeGzReader(MeshReader): class GCodeGzReader(MeshReader):
def __init__(self): def __init__(self, application):
super().__init__() super().__init__(application)
self._supported_extensions = [".gcode.gz"] self._supported_extensions = [".gcode.gz"]
def read(self, file_name): def _read(self, file_name):
with open(file_name, "rb") as file: with open(file_name, "rb") as file:
file_data = file.read() file_data = file.read()
uncompressed_gcode = gzip.decompress(file_data).decode("utf-8") uncompressed_gcode = gzip.decompress(file_data).decode("utf-8")

View file

@ -21,4 +21,4 @@ def getMetaData():
def register(app): def register(app):
app.addNonSliceableExtension(".gz") 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 UM.Message import Message
from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.CuraSceneNode import CuraSceneNode
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Preferences import Preferences
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
@ -47,7 +46,7 @@ class FlavorParser:
self._current_layer_thickness = 0.2 # default self._current_layer_thickness = 0.2 # default
self._filament_diameter = 2.85 # 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: def _clearValues(self) -> None:
self._extruder_number = 0 self._extruder_number = 0
@ -462,7 +461,7 @@ class FlavorParser:
Logger.log("d", "GCode loading finished") 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( caution_message = Message(catalog.i18nc(
"@info:generic", "@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."), "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.FileHandler.FileReader import FileReader
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Preferences import Preferences from UM.Application import Application
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
@ -27,12 +27,12 @@ class GCodeReader(MeshReader):
_flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(), _flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(),
"Marlin" : MarlinFlavorParser.MarlinFlavorParser()} "Marlin" : MarlinFlavorParser.MarlinFlavorParser()}
def __init__(self): def __init__(self, application):
super(GCodeReader, self).__init__() super(GCodeReader, self).__init__(application)
self._supported_extensions = [".gcode", ".g"] self._supported_extensions = [".gcode", ".g"]
self._flavor_reader = None 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): def preReadFromStream(self, stream, *args, **kwargs):
for line in stream.split("\n"): for line in stream.split("\n"):
@ -57,7 +57,7 @@ class GCodeReader(MeshReader):
def readFromStream(self, stream): def readFromStream(self, stream):
return self._flavor_reader.processGCodeStream(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: with open(file_name, "r", encoding = "utf-8") as file:
file_data = file.read() file_data = file.read()
return self.readFromStream(file_data) return self.readFromStream(file_data)

View file

@ -23,4 +23,4 @@ def getMetaData():
def register(app): def register(app):
app.addNonSliceableExtension(".gcode") app.addNonSliceableExtension(".gcode")
app.addNonSliceableExtension(".g") 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): class ImageReader(MeshReader):
def __init__(self): def __init__(self, application):
super(ImageReader, self).__init__() super(ImageReader, self).__init__(application)
self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
self._ui = ImageReaderUI(self) self._ui = ImageReaderUI(self)
@ -44,7 +44,7 @@ class ImageReader(MeshReader):
return MeshReader.PreReadResult.cancelled return MeshReader.PreReadResult.cancelled
return MeshReader.PreReadResult.accepted return MeshReader.PreReadResult.accepted
def read(self, file_name): def _read(self, file_name):
size = max(self._ui.getWidth(), self._ui.getDepth()) 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) 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): 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. self._caution_message = Message("", #Message text gets set when the message gets shown, to display the models in question.
lifetime = 0, 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().initializationFinished.connect(self._pluginsInitialized)
Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged) 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.Selection import Selection
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Application import Application from UM.Application import Application
from UM.Preferences import Preferences
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.SettingInstance import SettingInstance from UM.Settings.SettingInstance import SettingInstance
@ -27,7 +26,7 @@ class PerObjectSettingsTool(Tool):
Selection.selectionChanged.connect(self.propertyChanged) Selection.selectionChanged.connect(self.propertyChanged)
Preferences.getInstance().preferenceChanged.connect(self._onPreferenceChanged) Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferenceChanged)
self._onPreferenceChanged("cura/active_mode") self._onPreferenceChanged("cura/active_mode")
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
@ -106,7 +105,7 @@ class PerObjectSettingsTool(Tool):
def _onPreferenceChanged(self, preference): def _onPreferenceChanged(self, preference):
if preference == "cura/active_mode": if preference == "cura/active_mode":
self._advanced_mode = Preferences.getInstance().getValue(preference) == 1 self._advanced_mode = Application.getInstance().getPreferences().getValue(preference) == 1
self._updateEnabled() self._updateEnabled()
def _onGlobalContainerChanged(self): def _onGlobalContainerChanged(self):

View file

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

View file

@ -10,7 +10,6 @@ from PyQt5.QtCore import pyqtSlot, QObject
from UM.Extension import Extension from UM.Extension import Extension
from UM.Application import Application from UM.Application import Application
from UM.Preferences import Preferences
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Message import Message from UM.Message import Message
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -34,22 +33,23 @@ class SliceInfo(QObject, Extension):
QObject.__init__(self, parent) QObject.__init__(self, parent)
Extension.__init__(self) Extension.__init__(self)
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted) Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
Preferences.getInstance().addPreference("info/send_slice_info", True) Application.getInstance().getPreferences().addPreference("info/send_slice_info", True)
Preferences.getInstance().addPreference("info/asked_send_slice_info", False) Application.getInstance().getPreferences().addPreference("info/asked_send_slice_info", False)
self._more_info_dialog = None self._more_info_dialog = None
self._example_data_content = 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."), self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."),
lifetime = 0, lifetime = 0,
dismissable = False, dismissable = False,
title = catalog.i18nc("@info:title", "Collecting Data")) 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, 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) 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.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show() self.send_slice_info_message.show()
@ -62,7 +62,7 @@ class SliceInfo(QObject, Extension):
## Perform action based on user input. ## 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. # 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): 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": if action_id == "MoreInfo":
self.showMoreInfoDialog() self.showMoreInfoDialog()
self.send_slice_info_message.hide() self.send_slice_info_message.hide()
@ -88,11 +88,11 @@ class SliceInfo(QObject, Extension):
@pyqtSlot(bool) @pyqtSlot(bool)
def setSendSliceInfo(self, enabled: 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): def _onWriteStarted(self, output_device):
try: 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.") Logger.log("d", "'info/send_slice_info' is turned off.")
return # Do nothing, user does not want to send data return # Do nothing, user does not want to send data
@ -107,7 +107,7 @@ class SliceInfo(QObject, Extension):
data["schema_version"] = 0 data["schema_version"] = 0
data["cura_version"] = application.getVersion() 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: if active_mode == 0:
data["active_mode"] = "recommended" data["active_mode"] = "recommended"
else: else:
@ -122,7 +122,7 @@ class SliceInfo(QObject, Extension):
machine_settings_changed_by_user = True machine_settings_changed_by_user = True
data["machine_settings_changed_by_user"] = machine_settings_changed_by_user 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["os"] = {"type": platform.system(), "version": platform.version()}
data["active_machine"] = {"definition_id": global_stack.definition.getId(), 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.Scene.Selection import Selection
from UM.Resources import Resources from UM.Resources import Resources
from UM.Application import Application from UM.Application import Application
from UM.Preferences import Preferences
from UM.View.RenderBatch import RenderBatch from UM.View.RenderBatch import RenderBatch
from UM.Settings.Validator import ValidatorState from UM.Settings.Validator import ValidatorState
from UM.Math.Color import Color from UM.Math.Color import Color
@ -23,7 +22,7 @@ class SolidView(View):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
Preferences.getInstance().addPreference("view/show_overhang", True) Application.getInstance().getPreferences().addPreference("view/show_overhang", True)
self._enabled_shader = None self._enabled_shader = None
self._disabled_shader = None self._disabled_shader = None
@ -65,7 +64,7 @@ class SolidView(View):
support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr") support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(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") angle = support_angle_stack.getProperty("support_angle", "value")
# Make sure the overhang angle is valid before passing it to the shader # 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) # 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 frameVisible: false
selectionMode: 0 selectionMode: 0
model: packageData.supported_configs model: packageData.supported_configs
headerDelegate: Item headerDelegate: Rectangle
{ {
color: UM.Theme.getColor("sidebar")
height: UM.Theme.getSize("toolbox_chart_row").height height: UM.Theme.getSize("toolbox_chart_row").height
Label Label
{ {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ Item
{ {
color: UM.Theme.getColor("lining") color: UM.Theme.getColor("lining")
width: parent.width width: parent.width
height: UM.Theme.getSize("default_lining").height height: Math.floor(UM.Theme.getSize("default_lining").height)
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
} }
Row Row
@ -40,14 +40,14 @@ Item
Column Column
{ {
id: pluginInfo 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") 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 Label
{ {
text: model.name text: model.name
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
color: pluginInfo.color color: pluginInfo.color
@ -81,7 +81,7 @@ Item
} }
} }
width: parent.width width: parent.width
height: UM.Theme.getSize("toolbox_property_label").height height: Math.floor(UM.Theme.getSize("toolbox_property_label").height)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft

View file

@ -13,6 +13,16 @@ Column
width: UM.Theme.getSize("toolbox_action_button").width width: UM.Theme.getSize("toolbox_action_button").width
spacing: UM.Theme.getSize("narrow_margin").height 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 ToolboxProgressButton
{ {
id: updateButton id: updateButton
@ -39,7 +49,7 @@ Column
{ {
id: removeButton id: removeButton
text: canDowngrade ? catalog.i18nc("@action:button", "Downgrade") : catalog.i18nc("@action:button", "Uninstall") 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 enabled: !toolbox.isDownloading
style: ButtonStyle style: ButtonStyle
{ {

View file

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

View file

@ -29,8 +29,9 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 12, "last_updated") self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled") self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "is_enabled") self.addRoleName(Qt.UserRole + 14, "is_enabled")
self.addRoleName(Qt.UserRole + 15, "has_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, "supported_configs") 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. # List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str] 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, "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_bundled": package["is_bundled"] if "is_bundled" in package else False,
"is_enabled": package["is_enabled"] if "is_enabled" 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, "has_configs": has_configs,
"supported_configs": configs_model "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 ## The Toolbox class is responsible of communicating with the server through the API
class Toolbox(QObject, Extension): 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: def __init__(self, parent=None) -> None:
super().__init__(parent) super().__init__(parent)
@ -36,14 +37,11 @@ class Toolbox(QObject, Extension):
self._application = Application.getInstance() self._application = Application.getInstance()
self._package_manager = None self._package_manager = None
self._plugin_registry = Application.getInstance().getPluginRegistry() self._plugin_registry = Application.getInstance().getPluginRegistry()
self._packages_api_root = self._getPackagesApiRoot()
self._packages_version = self._getPackagesVersion() self._sdk_version = None
self._api_version = 1 self._cloud_api_version = None
self._api_url = "{api_root}/cura-packages/v{api_version}/cura/v{package_version}".format( self._cloud_api_root = None
api_root = self._packages_api_root, self._api_url = None
api_version = self._api_version,
package_version = self._packages_version
)
# Network: # Network:
self._get_packages_request = None self._get_packages_request = None
@ -64,21 +62,19 @@ class Toolbox(QObject, Extension):
) )
) )
] ]
self._request_urls = { 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._to_update = [] # Package_ids that are waiting to be updated self._to_update = [] # Package_ids that are waiting to be updated
self._old_plugin_ids = []
# Data: # Data:
self._metadata = { self._metadata = {
"authors": [], "authors": [],
"packages": [], "packages": [],
"plugins_showcase": [], "plugins_showcase": [],
"plugins_available": [],
"plugins_installed": [], "plugins_installed": [],
"materials_showcase": [], "materials_showcase": [],
"materials_available": [],
"materials_installed": [] "materials_installed": []
} }
@ -161,22 +157,52 @@ class Toolbox(QObject, Extension):
# this is initialized. Therefore, we wait until the application is ready. # this is initialized. Therefore, we wait until the application is ready.
def _onAppInitialized(self) -> None: def _onAppInitialized(self) -> None:
self._package_manager = Application.getInstance().getPackageManager() 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. # 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"): if not hasattr(cura, "CuraVersion"):
return self.DEFAULT_PACKAGES_API_ROOT return self.DEFAULT_CLOUD_API_ROOT
if not hasattr(cura.CuraVersion, "CuraPackagesApiRoot"): if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"):
return self.DEFAULT_PACKAGES_API_ROOT return self.DEFAULT_CLOUD_API_ROOT
return cura.CuraVersion.CuraPackagesApiRoot 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. # Get the packages version depending on Cura version settings.
def _getPackagesVersion(self) -> int: def _getSDKVersion(self) -> int:
if not hasattr(cura, "CuraVersion"): if not hasattr(cura, "CuraVersion"):
return self._plugin_registry.APIVersion return self._plugin_registry.APIVersion
if not hasattr(cura.CuraVersion, "CuraPackagesVersion"): if not hasattr(cura.CuraVersion, "CuraSDKVersion"):
return self._plugin_registry.APIVersion return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraPackagesVersion if not cura.CuraVersion.CuraSDKVersion:
return self._plugin_registry.APIVersion
return cura.CuraVersion.CuraSDKVersion
@pyqtSlot() @pyqtSlot()
def browsePackages(self) -> None: def browsePackages(self) -> None:
@ -212,15 +238,52 @@ class Toolbox(QObject, Extension):
dialog = Application.getInstance().createQmlComponent(path, {"toolbox": self}) dialog = Application.getInstance().createQmlComponent(path, {"toolbox": self})
return dialog 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() @pyqtSlot()
def _updateInstalledModels(self) -> None: 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() all_packages = self._package_manager.getAllInstalledPackagesInfo()
if "plugin" in all_packages: 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._models["plugins_installed"].setMetadata(self._metadata["plugins_installed"])
self.metadataChanged.emit() self.metadataChanged.emit()
if "material" in all_packages: if "material" in all_packages:
self._metadata["materials_installed"] = all_packages["material"] 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._models["materials_installed"].setMetadata(self._metadata["materials_installed"])
self.metadataChanged.emit() self.metadataChanged.emit()
@ -250,8 +313,6 @@ class Toolbox(QObject, Extension):
if remote_package: if remote_package:
download_url = remote_package["download_url"] download_url = remote_package["download_url"]
Logger.log("d", "Updating package [%s]..." % plugin_id) Logger.log("d", "Updating package [%s]..." % plugin_id)
if self._package_manager.isUserInstalledPackage(plugin_id):
self.uninstall(plugin_id)
self.startDownload(download_url) self.startDownload(download_url)
else: else:
Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id) 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) @pyqtSlot(str, result = bool)
def canUpdate(self, package_id: str) -> bool: def canUpdate(self, package_id: str) -> bool:
if self.isOldPlugin(package_id):
return True
local_package = self._package_manager.getInstalledPackageInfo(package_id) local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None: if local_package is None:
return False return False
@ -318,19 +382,21 @@ class Toolbox(QObject, Extension):
remote_version = Version(remote_package["package_version"]) remote_version = Version(remote_package["package_version"])
return remote_version > local_version return remote_version > local_version
@pyqtSlot(str, result=bool) @pyqtSlot(str, result = bool)
def canDowngrade(self, package_id: str) -> 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) local_package = self._package_manager.getInstalledPackageInfo(package_id)
if local_package is None: if local_package is None:
return False return False
remote_package = self.getRemotePackage(package_id) bundled_package = self._package_manager.getBundledPackageInfo(package_id)
if remote_package is None: if bundled_package is None:
return False return False
local_version = Version(local_package["package_version"]) local_version = Version(local_package["package_version"])
remote_version = Version(remote_package["package_version"]) bundled_version = Version(bundled_package["package_version"])
return remote_version < local_version return bundled_version < local_version
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def isInstalled(self, package_id: str) -> bool: def isInstalled(self, package_id: str) -> bool:
@ -342,6 +408,13 @@ class Toolbox(QObject, Extension):
return True return True
return False 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: def loadingComplete(self) -> bool:
populated = 0 populated = 0
for list in self._metadata.items(): for list in self._metadata.items():
@ -383,7 +456,10 @@ class Toolbox(QObject, Extension):
def resetDownload(self) -> None: def resetDownload(self) -> None:
if self._download_reply: if self._download_reply:
try:
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress) 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.abort()
self._download_reply = None self._download_reply = None
self._download_request = None self._download_request = None

View file

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

View file

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

View file

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

View file

@ -379,13 +379,14 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
def resumePrint(self): def resumePrint(self):
self._paused = False 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): def cancelPrint(self):
self._gcode_position = 0 self._gcode_position = 0
self._gcode.clear() self._gcode.clear()
self._printers[0].updateActivePrintJob(None) self._printers[0].updateActivePrintJob(None)
self._is_printing = False self._is_printing = False
self._is_paused = False self._paused = False
# Turn off temperatures, fan and steppers # Turn off temperatures, fan and steppers
self._sendCommand("M140 S0") 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. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Signal import Signal, signalemitter import threading
from UM.Application import Application import platform
from UM.Resources import Resources import time
import serial.tools.list_ports
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger from UM.Logger import Logger
from UM.Resources import Resources
from UM.Signal import Signal, signalemitter
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -12,12 +18,6 @@ from cura.PrinterOutputDevice import ConnectionState
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from . import USBPrinterOutputDevice 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") i18n_catalog = i18nCatalog("cura")
@ -28,8 +28,14 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
addUSBOutputDeviceSignal = Signal() addUSBOutputDeviceSignal = Signal()
progressChanged = pyqtSignal() 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) super().__init__(parent = parent)
self._application = application
self._serial_port_list = [] self._serial_port_list = []
self._usb_output_devices = {} self._usb_output_devices = {}
self._usb_output_devices_model = None self._usb_output_devices_model = None
@ -38,11 +44,11 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._check_updates = True 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. # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
self.addUSBOutputDeviceSignal.connect(self.addOutputDevice) 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 # The method updates/reset the USB settings for all connected USB devices
def updateUSBPrinterOutputDevices(self): def updateUSBPrinterOutputDevices(self):
@ -69,7 +75,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
def _updateThread(self): def _updateThread(self):
while self._check_updates: while self._check_updates:
container_stack = Application.getInstance().getGlobalContainerStack() container_stack = self._application.getGlobalContainerStack()
if container_stack is None: if container_stack is None:
time.sleep(5) time.sleep(5)
continue continue
@ -81,19 +87,10 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._addRemovePorts(port_list) self._addRemovePorts(port_list)
time.sleep(5) 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) @pyqtSlot(result = str)
def getDefaultFirmwareName(self): def getDefaultFirmwareName(self):
# Check if there is a valid global container stack # 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: if not global_container_stack:
Logger.log("e", "There is no global container stack. Can not update firmware.") Logger.log("e", "There is no global container stack. Can not update firmware.")
self._firmware_view.close() self._firmware_view.close()
@ -182,4 +179,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
return list(base_list) 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). # 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-) # 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) 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. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Extension import Extension import os
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
from PyQt5.QtCore import QObject, pyqtSlot from PyQt5.QtCore import QObject, pyqtSlot
import os.path from UM.Extension import Extension
from UM.Logger import Logger
class UserAgreement(QObject, Extension): class UserAgreement(QObject, Extension):
def __init__(self): def __init__(self, application):
super(UserAgreement, self).__init__() super(UserAgreement, self).__init__()
self._application = application
self._user_agreement_window = None self._user_agreement_window = None
self._user_agreement_context = None self._user_agreement_context = None
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) self._application.engineCreatedSignal.connect(self._onEngineCreated)
Preferences.getInstance().addPreference("general/accepted_user_agreement", False)
self._application.getPreferences().addPreference("general/accepted_user_agreement", False)
def _onEngineCreated(self): def _onEngineCreated(self):
if not Preferences.getInstance().getValue("general/accepted_user_agreement"): if not self._application.getPreferences().getValue("general/accepted_user_agreement"):
self.showUserAgreement() self.showUserAgreement()
def showUserAgreement(self): def showUserAgreement(self):
@ -35,14 +33,14 @@ class UserAgreement(QObject, Extension):
def didAgree(self, user_choice): def didAgree(self, user_choice):
if user_choice: if user_choice:
Logger.log("i", "User agreed to the user agreement") 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() self._user_agreement_window.hide()
else: else:
Logger.log("i", "User did NOT agree to the user agreement") Logger.log("i", "User did NOT agree to the user agreement")
Preferences.getInstance().setValue("general/accepted_user_agreement", False) self._application.getPreferences().setValue("general/accepted_user_agreement", False)
CuraApplication.getInstance().quit() self._application.quit()
CuraApplication.getInstance().setNeedToShowUserAgreement(False) self._application.setNeedToShowUserAgreement(False)
def createUserAgreementWindow(self): def createUserAgreementWindow(self):
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "UserAgreement.qml") path = os.path.join(self._application.getPluginRegistry().getPluginPath(self.getPluginId()), "UserAgreement.qml")
self._user_agreement_window = Application.getInstance().createQmlComponent(path, {"manager": self}) self._user_agreement_window = self._application.createQmlComponent(path, {"manager": self})

View file

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

View file

@ -107,7 +107,12 @@ class MachineInstance:
user_profile["values"] = {} user_profile["values"] = {}
version_upgrade_manager = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance() 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") 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): if not os.path.exists(user_storage):
os.makedirs(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. 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 ## Upgrades configurations from the state they were in at version 3.3 to the
# state they should be in at version 3.4. # state they should be in at version 3.4.
@ -38,6 +41,17 @@ class VersionUpgrade33to34(VersionUpgrade):
# Update version number. # Update version number.
parser["general"]["version"] = "4" 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() result = io.StringIO()
parser.write(result) parser.write(result)
return [filename], [result.getvalue()] return [filename], [result.getvalue()]

View file

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

View file

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

View file

@ -16,4 +16,4 @@ def getMetaData():
} }
def register(app): 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. # Cura is released under the terms of the LGPLv3 or higher.
import copy import copy
@ -12,10 +12,10 @@ import xml.etree.ElementTree as ET
from UM.Resources import Resources from UM.Resources import Resources
from UM.Logger import Logger from UM.Logger import Logger
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
import UM.Dictionary import UM.Dictionary
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from .XmlMaterialValidator import XmlMaterialValidator from .XmlMaterialValidator import XmlMaterialValidator
@ -540,7 +540,9 @@ class XmlMaterialProfile(InstanceContainer):
validation_message = XmlMaterialValidator.validateMaterialMetaData(meta_data) validation_message = XmlMaterialValidator.validateMaterialMetaData(meta_data)
if validation_message is not None: 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 = {} property_values = {}
properties = data.iterfind("./um:properties/*", self.__namespaces) properties = data.iterfind("./um:properties/*", self.__namespaces)

View file

@ -6,7 +6,7 @@
"display_name": "3MF Reader", "display_name": "3MF Reader",
"description": "Provides support for reading 3MF files.", "description": "Provides support for reading 3MF files.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -23,7 +23,7 @@
"display_name": "3MF Writer", "display_name": "3MF Writer",
"description": "Provides support for writing 3MF files.", "description": "Provides support for writing 3MF files.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -40,7 +40,7 @@
"display_name": "Change Log", "display_name": "Change Log",
"description": "Shows changes since latest checked version.", "description": "Shows changes since latest checked version.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -57,7 +57,7 @@
"display_name": "CuraEngine Backend", "display_name": "CuraEngine Backend",
"description": "Provides the link to the CuraEngine slicing backend.", "description": "Provides the link to the CuraEngine slicing backend.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -74,7 +74,7 @@
"display_name": "Cura Profile Reader", "display_name": "Cura Profile Reader",
"description": "Provides support for importing Cura profiles.", "description": "Provides support for importing Cura profiles.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -91,7 +91,7 @@
"display_name": "Cura Profile Writer", "display_name": "Cura Profile Writer",
"description": "Provides support for exporting Cura profiles.", "description": "Provides support for exporting Cura profiles.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -108,7 +108,7 @@
"display_name": "Firmware Update Checker", "display_name": "Firmware Update Checker",
"description": "Checks for firmware updates.", "description": "Checks for firmware updates.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -125,7 +125,7 @@
"display_name": "Compressed G-code Reader", "display_name": "Compressed G-code Reader",
"description": "Reads g-code from a compressed archive.", "description": "Reads g-code from a compressed archive.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -142,7 +142,7 @@
"display_name": "Compressed G-code Writer", "display_name": "Compressed G-code Writer",
"description": "Writes g-code to a compressed archive.", "description": "Writes g-code to a compressed archive.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -159,7 +159,7 @@
"display_name": "G-Code Profile Reader", "display_name": "G-Code Profile Reader",
"description": "Provides support for importing profiles from g-code files.", "description": "Provides support for importing profiles from g-code files.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -176,7 +176,7 @@
"display_name": "G-Code Reader", "display_name": "G-Code Reader",
"description": "Allows loading and displaying G-code files.", "description": "Allows loading and displaying G-code files.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "VictorLarchenko", "author_id": "VictorLarchenko",
@ -193,7 +193,7 @@
"display_name": "G-Code Writer", "display_name": "G-Code Writer",
"description": "Writes g-code to a file.", "description": "Writes g-code to a file.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -210,7 +210,7 @@
"display_name": "Image Reader", "display_name": "Image Reader",
"description": "Enables ability to generate printable geometry from 2D image files.", "description": "Enables ability to generate printable geometry from 2D image files.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -227,7 +227,7 @@
"display_name": "Legacy Cura Profile Reader", "display_name": "Legacy Cura Profile Reader",
"description": "Provides support for importing profiles from legacy Cura versions.", "description": "Provides support for importing profiles from legacy Cura versions.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -244,7 +244,7 @@
"display_name": "Machine Settings Action", "display_name": "Machine Settings Action",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).", "description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "fieldOfView", "author_id": "fieldOfView",
@ -261,7 +261,7 @@
"display_name": "Model Checker", "display_name": "Model Checker",
"description": "Checks models and print configuration for possible printing issues and give suggestions.", "description": "Checks models and print configuration for possible printing issues and give suggestions.",
"package_version": "0.1.0", "package_version": "0.1.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -278,7 +278,7 @@
"display_name": "Monitor Stage", "display_name": "Monitor Stage",
"description": "Provides a monitor stage in Cura.", "description": "Provides a monitor stage in Cura.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -295,7 +295,7 @@
"display_name": "Per-Object Settings Tool", "display_name": "Per-Object Settings Tool",
"description": "Provides the per-model settings.", "description": "Provides the per-model settings.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -312,7 +312,7 @@
"display_name": "Post Processing", "display_name": "Post Processing",
"description": "Extension that allows for user created scripts for post processing.", "description": "Extension that allows for user created scripts for post processing.",
"package_version": "2.2.0", "package_version": "2.2.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -329,7 +329,7 @@
"display_name": "Prepare Stage", "display_name": "Prepare Stage",
"description": "Provides a prepare stage in Cura.", "description": "Provides a prepare stage in Cura.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -346,7 +346,7 @@
"display_name": "Removable Drive Output Device", "display_name": "Removable Drive Output Device",
"description": "Provides removable drive hotplugging and writing support.", "description": "Provides removable drive hotplugging and writing support.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -363,7 +363,7 @@
"display_name": "Simulation View", "display_name": "Simulation View",
"description": "Provides the Simulation view.", "description": "Provides the Simulation view.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -380,7 +380,7 @@
"display_name": "Slice Info", "display_name": "Slice Info",
"description": "Submits anonymous slice info. Can be disabled through preferences.", "description": "Submits anonymous slice info. Can be disabled through preferences.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -397,7 +397,7 @@
"display_name": "Solid View", "display_name": "Solid View",
"description": "Provides a normal solid mesh view.", "description": "Provides a normal solid mesh view.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -414,7 +414,7 @@
"display_name": "Support Eraser Tool", "display_name": "Support Eraser Tool",
"description": "Creates an eraser mesh to block the printing of support in certain places.", "description": "Creates an eraser mesh to block the printing of support in certain places.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -431,7 +431,7 @@
"display_name": "Toolbox", "display_name": "Toolbox",
"description": "Find, manage and install new Cura packages.", "description": "Find, manage and install new Cura packages.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -448,7 +448,7 @@
"display_name": "UFP Writer", "display_name": "UFP Writer",
"description": "Provides support for writing Ultimaker Format Packages.", "description": "Provides support for writing Ultimaker Format Packages.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -465,7 +465,7 @@
"display_name": "Ultimaker Machine Actions", "display_name": "Ultimaker Machine Actions",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).", "description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -482,7 +482,7 @@
"display_name": "UM3 Network Printing", "display_name": "UM3 Network Printing",
"description": "Manages network connections to Ultimaker 3 printers.", "description": "Manages network connections to Ultimaker 3 printers.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -499,7 +499,7 @@
"display_name": "USB Printing", "display_name": "USB Printing",
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.", "description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -516,7 +516,7 @@
"display_name": "User Agreement", "display_name": "User Agreement",
"description": "Ask the user once if he/she agrees with our license.", "description": "Ask the user once if he/she agrees with our license.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -533,7 +533,7 @@
"display_name": "Version Upgrade 2.1 to 2.2", "display_name": "Version Upgrade 2.1 to 2.2",
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.", "description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -550,7 +550,7 @@
"display_name": "Version Upgrade 2.2 to 2.4", "display_name": "Version Upgrade 2.2 to 2.4",
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.", "description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -567,7 +567,7 @@
"display_name": "Version Upgrade 2.5 to 2.6", "display_name": "Version Upgrade 2.5 to 2.6",
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.", "description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -584,7 +584,7 @@
"display_name": "Version Upgrade 2.6 to 2.7", "display_name": "Version Upgrade 2.6 to 2.7",
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.", "description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -601,7 +601,7 @@
"display_name": "Version Upgrade 2.7 to 3.0", "display_name": "Version Upgrade 2.7 to 3.0",
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.", "description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -618,7 +618,7 @@
"display_name": "Version Upgrade 3.0 to 3.1", "display_name": "Version Upgrade 3.0 to 3.1",
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.", "description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -635,7 +635,7 @@
"display_name": "Version Upgrade 3.2 to 3.3", "display_name": "Version Upgrade 3.2 to 3.3",
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.", "description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -652,7 +652,7 @@
"display_name": "Version Upgrade 3.3 to 3.4", "display_name": "Version Upgrade 3.3 to 3.4",
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.", "description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -669,7 +669,7 @@
"display_name": "X3D Reader", "display_name": "X3D Reader",
"description": "Provides support for reading X3D files.", "description": "Provides support for reading X3D files.",
"package_version": "0.5.0", "package_version": "0.5.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "SevaAlekseyev", "author_id": "SevaAlekseyev",
@ -686,7 +686,7 @@
"display_name": "XML Material Profiles", "display_name": "XML Material Profiles",
"description": "Provides capabilities to read and write XML-based material profiles.", "description": "Provides capabilities to read and write XML-based material profiles.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -703,7 +703,7 @@
"display_name": "X-Ray View", "display_name": "X-Ray View",
"description": "Provides the X-Ray view.", "description": "Provides the X-Ray view.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -720,7 +720,7 @@
"display_name": "Dagoma Chromatik PLA", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://dagoma.fr/boutique/filaments.html", "website": "https://dagoma.fr/boutique/filaments.html",
"author": { "author": {
"author_id": "Dagoma", "author_id": "Dagoma",
@ -737,7 +737,7 @@
"display_name": "FABtotum ABS", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40", "website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=40",
"author": { "author": {
"author_id": "FABtotum", "author_id": "FABtotum",
@ -754,7 +754,7 @@
"display_name": "FABtotum Nylon", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53", "website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=53",
"author": { "author": {
"author_id": "FABtotum", "author_id": "FABtotum",
@ -771,7 +771,7 @@
"display_name": "FABtotum PLA", "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°.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39", "website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=39",
"author": { "author": {
"author_id": "FABtotum", "author_id": "FABtotum",
@ -788,7 +788,7 @@
"display_name": "FABtotum TPU Shore 98A", "display_name": "FABtotum TPU Shore 98A",
"description": "", "description": "",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66", "website": "https://store.fabtotum.com/eu/products/filaments.html?filament_type=66",
"author": { "author": {
"author_id": "FABtotum", "author_id": "FABtotum",
@ -805,7 +805,7 @@
"display_name": "Fiberlogy HD PLA", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/", "website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
"author": { "author": {
"author_id": "Fiberlogy", "author_id": "Fiberlogy",
@ -822,7 +822,7 @@
"display_name": "Filo3D PLA", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://dagoma.fr", "website": "https://dagoma.fr",
"author": { "author": {
"author_id": "Dagoma", "author_id": "Dagoma",
@ -839,7 +839,7 @@
"display_name": "IMADE3D JellyBOX PETG", "display_name": "IMADE3D JellyBOX PETG",
"description": "", "description": "",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "http://shop.imade3d.com/filament.html", "website": "http://shop.imade3d.com/filament.html",
"author": { "author": {
"author_id": "IMADE3D", "author_id": "IMADE3D",
@ -856,7 +856,7 @@
"display_name": "IMADE3D JellyBOX PLA", "display_name": "IMADE3D JellyBOX PLA",
"description": "", "description": "",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "http://shop.imade3d.com/filament.html", "website": "http://shop.imade3d.com/filament.html",
"author": { "author": {
"author_id": "IMADE3D", "author_id": "IMADE3D",
@ -873,7 +873,7 @@
"display_name": "Octofiber PLA", "display_name": "Octofiber PLA",
"description": "PLA material from Octofiber.", "description": "PLA material from Octofiber.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://nl.octofiber.com/3d-printing-filament/pla.html", "website": "https://nl.octofiber.com/3d-printing-filament/pla.html",
"author": { "author": {
"author_id": "Octofiber", "author_id": "Octofiber",
@ -890,7 +890,7 @@
"display_name": "PolyFlex™ PLA", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "http://www.polymaker.com/shop/polyflex/", "website": "http://www.polymaker.com/shop/polyflex/",
"author": { "author": {
"author_id": "Polymaker", "author_id": "Polymaker",
@ -907,7 +907,7 @@
"display_name": "PolyMax™ PLA", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "http://www.polymaker.com/shop/polymax/", "website": "http://www.polymaker.com/shop/polymax/",
"author": { "author": {
"author_id": "Polymaker", "author_id": "Polymaker",
@ -924,7 +924,7 @@
"display_name": "PolyPlus™ PLA True Colour", "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.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "http://www.polymaker.com/shop/polyplus-true-colour/", "website": "http://www.polymaker.com/shop/polyplus-true-colour/",
"author": { "author": {
"author_id": "Polymaker", "author_id": "Polymaker",
@ -941,7 +941,7 @@
"display_name": "PolyWood™ PLA", "display_name": "PolyWood™ PLA",
"description": "PolyWood™ is a wood mimic printing material that contains no actual wood ensuring a clean Jam-Free™ printing experience.", "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", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "http://www.polymaker.com/shop/polywood/", "website": "http://www.polymaker.com/shop/polywood/",
"author": { "author": {
"author_id": "Polymaker", "author_id": "Polymaker",
@ -958,7 +958,7 @@
"display_name": "Ultimaker ABS", "display_name": "Ultimaker ABS",
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/products/materials/abs",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -977,7 +977,7 @@
"display_name": "Ultimaker CPE", "display_name": "Ultimaker CPE",
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/products/materials/abs",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -996,7 +996,7 @@
"display_name": "Ultimaker Nylon", "display_name": "Ultimaker Nylon",
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/products/materials/abs",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -1015,7 +1015,7 @@
"display_name": "Ultimaker PC", "display_name": "Ultimaker PC",
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com/products/materials/pc", "website": "https://ultimaker.com/products/materials/pc",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -1034,7 +1034,7 @@
"display_name": "Ultimaker PLA", "display_name": "Ultimaker PLA",
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/products/materials/abs",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -1053,7 +1053,7 @@
"display_name": "Ultimaker PVA", "display_name": "Ultimaker PVA",
"description": "Example package for material and quality profiles for Ultimaker materials.", "description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://ultimaker.com/products/materials/abs", "website": "https://ultimaker.com/products/materials/abs",
"author": { "author": {
"author_id": "Ultimaker", "author_id": "Ultimaker",
@ -1072,7 +1072,7 @@
"display_name": "Vertex Delta ABS", "display_name": "Vertex Delta ABS",
"description": "ABS material and quality files for the Delta Vertex K8800.", "description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://vertex3dprinter.eu", "website": "https://vertex3dprinter.eu",
"author": { "author": {
"author_id": "Velleman", "author_id": "Velleman",
@ -1089,7 +1089,7 @@
"display_name": "Vertex Delta PET", "display_name": "Vertex Delta PET",
"description": "ABS material and quality files for the Delta Vertex K8800.", "description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://vertex3dprinter.eu", "website": "https://vertex3dprinter.eu",
"author": { "author": {
"author_id": "Velleman", "author_id": "Velleman",
@ -1106,7 +1106,7 @@
"display_name": "Vertex Delta PLA", "display_name": "Vertex Delta PLA",
"description": "ABS material and quality files for the Delta Vertex K8800.", "description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://vertex3dprinter.eu", "website": "https://vertex3dprinter.eu",
"author": { "author": {
"author_id": "Velleman", "author_id": "Velleman",
@ -1123,7 +1123,7 @@
"display_name": "Vertex Delta TPU", "display_name": "Vertex Delta TPU",
"description": "ABS material and quality files for the Delta Vertex K8800.", "description": "ABS material and quality files for the Delta Vertex K8800.",
"package_version": "1.0.0", "package_version": "1.0.0",
"cura_version": 4, "sdk_version": 4,
"website": "https://vertex3dprinter.eu", "website": "https://vertex3dprinter.eu",
"author": { "author": {
"author_id": "Velleman", "author_id": "Velleman",
@ -1132,5 +1132,277 @@
"website": "https://www.vellemanprojects.eu" "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": "fill_perimeter_gaps":
{ {
"label": "Fill Gaps Between Walls", "label": "Fill Gaps Between Walls",
@ -1807,6 +1830,30 @@
"limit_to_extruder": "infill_extruder_nr", "limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true "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": "skin_preshrink":
{ {
"label": "Skin Removal Width", "label": "Skin Removal Width",
@ -3597,7 +3644,7 @@
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.", "description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "-1", "value": "defaultExtruderPosition()",
"enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1", "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": 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.", "description": "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion.",
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "-1", "value": "defaultExtruderPosition()",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('adhesion_type') != 'none'", "enabled": "extruders_enabled_count > 1 and resolveOrValue('adhesion_type') != 'none'",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
@ -5772,16 +5819,6 @@
"limit_to_extruder": "infill_extruder_nr", "limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true "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": "cross_infill_density_image":
{ {
"label": "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.", "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", "type": "str",
"default_value": "", "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", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
@ -5928,14 +5965,6 @@
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": true "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": "magic_fuzzy_skin_enabled":
{ {
"label": "Fuzzy Skin", "label": "Fuzzy Skin",
@ -6672,14 +6701,6 @@
"type": "float", "type": "float",
"enabled": "bridge_settings_enabled and bridge_enable_more_layers", "enabled": "bridge_settings_enabled and bridge_enable_more_layers",
"settable_per_mesh": true "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, "visible": true,
"author": "Ruben Dulek", "author": "Ruben Dulek",
"manufacturer": "Malyan", "manufacturer": "Malyan",
"machine_x3g_variant": "r1d",
"file_formats": "application/x3g" "file_formats": "application/x3g"
}, },

View file

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

View file

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

View file

@ -323,10 +323,11 @@ UM.MainWindow
{ {
if (drop.urls.length > 0) 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")) if (filename.endsWith(".curapackage"))
{ {
// Try to install plugin & close. // Try to install plugin & close.
@ -334,11 +335,13 @@ UM.MainWindow
packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting."); packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting.");
packageInstallDialog.icon = StandardIcon.Information; packageInstallDialog.icon = StandardIcon.Information;
packageInstallDialog.open(); packageInstallDialog.open();
return; }
else
{
nonPackages.push(filename);
} }
} }
openDialog.handleOpenFileUrls(nonPackages);
openDialog.handleOpenFileUrls(drop.urls);
} }
} }
} }

View file

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

View file

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

View file

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

View file

@ -13,7 +13,7 @@ Button
id: base id: base
property var outputDevice: null property var outputDevice: null
property var matched: updateOnSync() 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 width: parent.width
height: parent.height height: parent.height

View file

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

View file

@ -404,10 +404,17 @@ TabView
id: spinBox id: spinBox
anchors.left: label.right anchors.left: label.right
value: { value: {
// In case the setting is not in the material...
if (!isNaN(parseFloat(materialPropertyProvider.properties.value))) if (!isNaN(parseFloat(materialPropertyProvider.properties.value)))
{ {
return 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))) if (!isNaN(parseFloat(machinePropertyProvider.properties.value)))
{ {
return parseFloat(machinePropertyProvider.properties.value); return parseFloat(machinePropertyProvider.properties.value);
@ -431,6 +438,13 @@ TabView
key: model.key key: model.key
} }
UM.ContainerPropertyProvider UM.ContainerPropertyProvider
{
id: variantPropertyProvider
containerId: Cura.MachineManager.activeVariantId
watchedProperties: [ "value" ]
key: model.key
}
UM.ContainerPropertyProvider
{ {
id: machinePropertyProvider id: machinePropertyProvider
containerId: Cura.MachineManager.activeDefinitionId containerId: Cura.MachineManager.activeDefinitionId

View file

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

View file

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

View file

@ -150,7 +150,20 @@ UM.Dialog
width: parent.width width: parent.width
Label 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 font.bold: true
} }
Row Row

View file

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

View file

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

View file

@ -532,7 +532,7 @@ QtObject {
SequentialAnimation on x { SequentialAnimation on x {
id: xAnim 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 running: control.indeterminate && control.visible
loops: Animation.Infinite loops: Animation.Infinite
NumberAnimation { from: 0; to: xAnim.animEndPoint; duration: 2000;} NumberAnimation { from: 0; to: xAnim.animEndPoint; duration: 2000;}