Merge remote-tracking branch 'upstream/master' into mb-improve-overhanging-walls

This commit is contained in:
Mark Burton 2018-07-26 21:07:17 +01:00
commit aa8535f374
328 changed files with 2585 additions and 1226 deletions

1
Jenkinsfile vendored
View File

@ -1,5 +1,6 @@
parallel_nodes(['linux && cura', 'windows && cura']) { parallel_nodes(['linux && cura', 'windows && cura']) {
timeout(time: 2, unit: "HOURS") { timeout(time: 2, unit: "HOURS") {
// Prepare building // Prepare building
stage('Prepare') { stage('Prepare') {
// Ensure we start with a clean build directory. // Ensure we start with a clean build directory.

View File

@ -1,10 +1,13 @@
[Desktop Entry] [Desktop Entry]
Name=Ultimaker Cura Name=Ultimaker Cura
Name[de]=Ultimaker Cura Name[de]=Ultimaker Cura
Name[nl]=Ultimaker Cura
GenericName=3D Printing Software GenericName=3D Printing Software
GenericName[de]=3D-Druck-Software GenericName[de]=3D-Druck-Software
GenericName[nl]=3D-printsoftware
Comment=Cura converts 3D models into paths for a 3D printer. It prepares your print for maximum accuracy, minimum printing time and good reliability with many extra features that make your print come out great. Comment=Cura converts 3D models into paths for a 3D printer. It prepares your print for maximum accuracy, minimum printing time and good reliability with many extra features that make your print come out great.
Comment[de]=Cura wandelt 3D-Modelle in Pfade für einen 3D-Drucker um. Es bereitet Ihren Druck für maximale Genauigkeit, minimale Druckzeit und guter Zuverlässigkeit mit vielen zusätzlichen Funktionen vor, damit Ihr Druck großartig wird. Comment[de]=Cura wandelt 3D-Modelle in Pfade für einen 3D-Drucker um. Es bereitet Ihren Druck für maximale Genauigkeit, minimale Druckzeit und guter Zuverlässigkeit mit vielen zusätzlichen Funktionen vor, damit Ihr Druck großartig wird.
Comment[nl]=Cura converteert 3D-modellen naar paden voor een 3D printer. Het bereidt je print voor om zeer precies, snel en betrouwbaar te kunnen printen, met veel extra functionaliteit om je print er goed uit te laten komen.
Exec=@CMAKE_INSTALL_FULL_BINDIR@/cura %F Exec=@CMAKE_INSTALL_FULL_BINDIR@/cura %F
TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura
Icon=cura-icon Icon=cura-icon
@ -12,4 +15,4 @@ Terminal=false
Type=Application Type=Application
MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml; MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;
Categories=Graphics; Categories=Graphics;
Keywords=3D;Printing; Keywords=3D;Printing;Slicer;

View File

@ -1,12 +1,13 @@
# 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 io import io
import os import os
import re import re
import shutil import shutil
from typing import Optional from typing import Dict, Optional
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
from UM import i18nCatalog from UM import i18nCatalog
@ -28,9 +29,9 @@ class Backup:
# Re-use translation catalog. # Re-use translation catalog.
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
def __init__(self, zip_file: bytes = None, meta_data: dict = None) -> None: def __init__(self, zip_file: bytes = None, meta_data: Dict[str, str] = None) -> None:
self.zip_file = zip_file # type: Optional[bytes] self.zip_file = zip_file # type: Optional[bytes]
self.meta_data = meta_data # type: Optional[dict] self.meta_data = meta_data # type: Optional[Dict[str, str]]
## Create a back-up from the current user config folder. ## Create a back-up from the current user config folder.
def makeFromCurrent(self) -> None: def makeFromCurrent(self) -> None:

View File

@ -1,6 +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 typing import Optional, Tuple
from typing import Dict, Optional, Tuple
from UM.Logger import Logger from UM.Logger import Logger
from cura.Backups.Backup import Backup from cura.Backups.Backup import Backup
@ -18,7 +19,7 @@ class BackupsManager:
## Get a back-up of the current configuration. ## Get a back-up of the current configuration.
# \return A tuple containing a ZipFile (the actual back-up) and a dict # \return A tuple containing a ZipFile (the actual back-up) and a dict
# containing some metadata (like version). # containing some metadata (like version).
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]: def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]:
self._disableAutoSave() self._disableAutoSave()
backup = Backup() backup = Backup()
backup.makeFromCurrent() backup.makeFromCurrent()
@ -30,7 +31,7 @@ class BackupsManager:
# \param zip_file A bytes object containing the actual back-up. # \param zip_file A bytes object containing the actual back-up.
# \param meta_data A dict containing some metadata that is needed to # \param meta_data A dict containing some metadata that is needed to
# restore the back-up correctly. # restore the back-up correctly.
def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None: def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, str]) -> None:
if not meta_data.get("cura_release", None): if not meta_data.get("cura_release", None):
# If there is no "cura_release" specified in the meta data, we don't execute a backup restore. # If there is no "cura_release" specified in the meta data, we don't execute a backup restore.
Logger.log("w", "Tried to restore a backup without specifying a Cura version number.") Logger.log("w", "Tried to restore a backup without specifying a Cura version number.")
@ -43,13 +44,13 @@ class BackupsManager:
if restored: if restored:
# At this point, Cura will need to restart for the changes to take effect. # At this point, Cura will need to restart for the changes to take effect.
# We don't want to store the data at this point as that would override the just-restored backup. # We don't want to store the data at this point as that would override the just-restored backup.
self._application.windowClosed(save_data=False) self._application.windowClosed(save_data = False)
## Here we try to disable the auto-save plug-in as it might interfere with ## Here we try to disable the auto-save plug-in as it might interfere with
# restoring a back-up. # restoring a back-up.
def _disableAutoSave(self): def _disableAutoSave(self) -> None:
self._application.setSaveDataEnabled(False) self._application.setSaveDataEnabled(False)
## Re-enable auto-save after we're done. ## Re-enable auto-save after we're done.
def _enableAutoSave(self): def _enableAutoSave(self) -> None:
self._application.setSaveDataEnabled(True) self._application.setSaveDataEnabled(True)

View File

@ -47,10 +47,10 @@ class BuildVolume(SceneNode):
self._disallowed_area_color = None self._disallowed_area_color = None
self._error_area_color = None self._error_area_color = None
self._width = 0 self._width = 0 #type: float
self._height = 0 self._height = 0 #type: float
self._depth = 0 self._depth = 0 #type: float
self._shape = "" self._shape = "" #type: str
self._shader = None self._shader = None
@ -154,19 +154,19 @@ class BuildVolume(SceneNode):
if active_extruder_changed is not None: if active_extruder_changed is not None:
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild) active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
def setWidth(self, width): def setWidth(self, width: float) -> None:
if width is not None: if width is not None:
self._width = width self._width = width
def setHeight(self, height): def setHeight(self, height: float) -> None:
if height is not None: if height is not None:
self._height = height self._height = height
def setDepth(self, depth): def setDepth(self, depth: float) -> None:
if depth is not None: if depth is not None:
self._depth = depth self._depth = depth
def setShape(self, shape: str): def setShape(self, shape: str) -> None:
if shape: if shape:
self._shape = shape self._shape = shape
@ -294,7 +294,7 @@ 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 self._application._qml_engine: if not self._engine_ready:
return return
if not self._volume_outline_color: if not self._volume_outline_color:

View File

@ -5,6 +5,7 @@ import copy
import os import os
import sys import sys
import time import time
from typing import cast, TYPE_CHECKING, Optional
import numpy import numpy
@ -13,8 +14,6 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from typing import cast, TYPE_CHECKING
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Scene.Camera import Camera from UM.Scene.Camera import Camera
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
@ -84,7 +83,6 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager from cura.Machines.VariantManager import VariantManager
from plugins.SliceInfoPlugin.SliceInfo import SliceInfo
from .SingleInstance import SingleInstance from .SingleInstance import SingleInstance
from .AutoSave import AutoSave from .AutoSave import AutoSave
@ -98,6 +96,8 @@ from . import CuraSplashScreen
from . import CameraImageProvider from . import CameraImageProvider
from . import MachineActionManager from . import MachineActionManager
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
from cura.Settings.MachineManager import MachineManager from cura.Settings.MachineManager import MachineManager
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel from cura.Settings.UserChangesModel import UserChangesModel
@ -110,6 +110,10 @@ from cura.ObjectsModel import ObjectsModel
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
if TYPE_CHECKING:
from plugins.SliceInfoPlugin.SliceInfo import SliceInfo
numpy.seterr(all = "ignore") numpy.seterr(all = "ignore")
try: try:
@ -155,6 +159,8 @@ class CuraApplication(QtApplication):
self._boot_loading_time = time.time() self._boot_loading_time = time.time()
self._on_exit_callback_manager = OnExitCallbackManager(self)
# Variables set from CLI # Variables set from CLI
self._files_to_open = [] self._files_to_open = []
self._use_single_instance = False self._use_single_instance = False
@ -276,6 +282,8 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager(self) self._machine_action_manager = MachineActionManager.MachineActionManager(self)
self._machine_action_manager.initialize() self._machine_action_manager.initialize()
self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
def __sendCommandToSingleInstance(self): def __sendCommandToSingleInstance(self):
self._single_instance = SingleInstance(self, self._files_to_open) self._single_instance = SingleInstance(self, self._files_to_open)
@ -359,35 +367,35 @@ class CuraApplication(QtApplication):
empty_definition_changes_container = copy.deepcopy(empty_container) empty_definition_changes_container = copy.deepcopy(empty_container)
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes") empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
empty_definition_changes_container.addMetaDataEntry("type", "definition_changes") empty_definition_changes_container.setMetaDataEntry("type", "definition_changes")
self._container_registry.addContainer(empty_definition_changes_container) self._container_registry.addContainer(empty_definition_changes_container)
self.empty_definition_changes_container = empty_definition_changes_container self.empty_definition_changes_container = empty_definition_changes_container
empty_variant_container = copy.deepcopy(empty_container) empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant") empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant") empty_variant_container.setMetaDataEntry("type", "variant")
self._container_registry.addContainer(empty_variant_container) self._container_registry.addContainer(empty_variant_container)
self.empty_variant_container = empty_variant_container self.empty_variant_container = empty_variant_container
empty_material_container = copy.deepcopy(empty_container) empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material") empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material") empty_material_container.setMetaDataEntry("type", "material")
self._container_registry.addContainer(empty_material_container) self._container_registry.addContainer(empty_material_container)
self.empty_material_container = empty_material_container self.empty_material_container = empty_material_container
empty_quality_container = copy.deepcopy(empty_container) empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality") empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported") empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported") empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality") empty_quality_container.setMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False) empty_quality_container.setMetaDataEntry("supported", False)
self._container_registry.addContainer(empty_quality_container) self._container_registry.addContainer(empty_quality_container)
self.empty_quality_container = empty_quality_container self.empty_quality_container = empty_quality_container
empty_quality_changes_container = copy.deepcopy(empty_container) empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") empty_quality_changes_container.setMetaDataEntry("type", "quality_changes")
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported") empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported")
self._container_registry.addContainer(empty_quality_changes_container) self._container_registry.addContainer(empty_quality_changes_container)
self.empty_quality_changes_container = empty_quality_changes_container self.empty_quality_changes_container = empty_quality_changes_container
@ -407,43 +415,6 @@ class CuraApplication(QtApplication):
} }
) )
"""
self._currently_loading_files = []
self._non_sliceable_extensions = []
self._machine_action_manager = MachineActionManager.MachineActionManager()
self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None
self._material_manager = None
self._quality_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._auto_save = None
self._save_data_enabled = True
self._additional_components = {} # Components to add to certain areas in the interface
super().__init__(name = "cura",
version = CuraVersion,
buildtype = CuraBuildType,
is_debug_mode = CuraDebugMode,
tray_icon_name = "cura-icon-32.png",
**kwargs)
# FOR TESTING ONLY
if kwargs["parsed_command_line"].get("trigger_early_crash", False):
assert not "This crash is triggered by the trigger_early_crash command line argument."
self._variant_manager = None
self.default_theme = "cura-light"
"""
# Runs preparations that needs to be done before the starting process. # Runs preparations that needs to be done before the starting process.
def startSplashWindowPhase(self): def startSplashWindowPhase(self):
super().startSplashWindowPhase() super().startSplashWindowPhase()
@ -554,8 +525,8 @@ class CuraApplication(QtApplication):
def setNeedToShowUserAgreement(self, set_value = True): def setNeedToShowUserAgreement(self, set_value = True):
self._need_to_show_user_agreement = set_value self._need_to_show_user_agreement = set_value
## The "Quit" button click event handler. # DO NOT call this function to close the application, use checkAndExitApplication() instead which will perform
@pyqtSlot() # pre-exit checks such as checking for in-progress USB printing, etc.
def closeApplication(self): def closeApplication(self):
Logger.log("i", "Close application") Logger.log("i", "Close application")
main_window = self.getMainWindow() main_window = self.getMainWindow()
@ -564,6 +535,32 @@ class CuraApplication(QtApplication):
else: else:
self.exit(0) self.exit(0)
# This function first performs all upon-exit checks such as USB printing that is in progress.
# Use this to close the application.
@pyqtSlot()
def checkAndExitApplication(self) -> None:
self._on_exit_callback_manager.resetCurrentState()
self._on_exit_callback_manager.triggerNextCallback()
@pyqtSlot(result = bool)
def getIsAllChecksPassed(self) -> bool:
return self._on_exit_callback_manager.getIsAllChecksPassed()
def getOnExitCallbackManager(self) -> "OnExitCallbackManager":
return self._on_exit_callback_manager
def triggerNextExitCheck(self) -> None:
self._on_exit_callback_manager.triggerNextCallback()
showConfirmExitDialog = pyqtSignal(str, arguments = ["message"])
def setConfirmExitDialogCallback(self, callback):
self._confirm_exit_dialog_callback = callback
@pyqtSlot(bool)
def callConfirmExitDialogCallback(self, yes_or_no: bool):
self._confirm_exit_dialog_callback(yes_or_no)
## Signal to connect preferences action in QML ## Signal to connect preferences action in QML
showPreferencesWindow = pyqtSignal() showPreferencesWindow = pyqtSignal()
@ -1565,7 +1562,7 @@ class CuraApplication(QtApplication):
self.callLater(self.openProjectFile.emit, file) self.callLater(self.openProjectFile.emit, file)
return return
if Preferences.getInstance().getValue("cura/select_models_on_load"): if self.getPreferences().getValue("cura/select_models_on_load"):
Selection.clear() Selection.clear()
f = file.toLocalFile() f = file.toLocalFile()
@ -1602,10 +1599,12 @@ class CuraApplication(QtApplication):
def _readMeshFinished(self, job): def _readMeshFinished(self, job):
nodes = job.getResult() nodes = job.getResult()
filename = job.getFileName() file_name = job.getFileName()
self._currently_loading_files.remove(filename) file_name_lower = file_name.lower()
file_extension = file_name_lower.split(".")[-1]
self._currently_loading_files.remove(file_name)
self.fileLoaded.emit(filename) self.fileLoaded.emit(file_name)
arrange_objects_on_load = not self.getPreferences().getValue("cura/use_multi_build_plate") arrange_objects_on_load = not self.getPreferences().getValue("cura/use_multi_build_plate")
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
@ -1622,7 +1621,7 @@ class CuraApplication(QtApplication):
default_extruder_position = self.getMachineManager().defaultExtruderPosition default_extruder_position = self.getMachineManager().defaultExtruderPosition
default_extruder_id = self._global_container_stack.extruders[default_extruder_position].getId() default_extruder_id = self._global_container_stack.extruders[default_extruder_position].getId()
select_models_on_load = Preferences.getInstance().getValue("cura/select_models_on_load") select_models_on_load = self.getPreferences().getValue("cura/select_models_on_load")
for original_node in nodes: for original_node in nodes:
@ -1638,15 +1637,11 @@ class CuraApplication(QtApplication):
node.scale(original_node.getScale()) node.scale(original_node.getScale())
node.setSelectable(True) node.setSelectable(True)
node.setName(os.path.basename(filename)) node.setName(os.path.basename(file_name))
self.getBuildVolume().checkBoundsAndUpdate(node) self.getBuildVolume().checkBoundsAndUpdate(node)
is_non_sliceable = False is_non_sliceable = "." + file_extension in self._non_sliceable_extensions
filename_lower = filename.lower()
for extension in self._non_sliceable_extensions:
if filename_lower.endswith(extension):
is_non_sliceable = True
break
if is_non_sliceable: if is_non_sliceable:
self.callLater(lambda: self.getController().setActiveView("SimulationView")) self.callLater(lambda: self.getController().setActiveView("SimulationView"))
@ -1665,7 +1660,7 @@ class CuraApplication(QtApplication):
if not child.getDecorator(ConvexHullDecorator): if not child.getDecorator(ConvexHullDecorator):
child.addDecorator(ConvexHullDecorator()) child.addDecorator(ConvexHullDecorator())
if arrange_objects_on_load: if file_extension != "3mf" and arrange_objects_on_load:
if node.callDecoration("isSliceable"): if node.callDecoration("isSliceable"):
# Only check position if it's not already blatantly obvious that it won't fit. # Only check position if it's not already blatantly obvious that it won't fit.
if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth: if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
@ -1699,7 +1694,7 @@ class CuraApplication(QtApplication):
if select_models_on_load: if select_models_on_load:
Selection.add(node) Selection.add(node)
self.fileCompleted.emit(filename) self.fileCompleted.emit(file_name)
def addNonSliceableExtension(self, extension): def addNonSliceableExtension(self, extension):
self._non_sliceable_extensions.append(extension) self._non_sliceable_extensions.append(extension)

View File

@ -1,7 +1,11 @@
# 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 List, Tuple
from cura.CuraApplication import CuraApplication #To find some resource types. from cura.CuraApplication import CuraApplication #To find some resource types.
from cura.Settings.GlobalStack import GlobalStack
from UM.PackageManager import PackageManager #The class we're extending. from UM.PackageManager import PackageManager #The class we're extending.
from UM.Resources import Resources #To find storage paths for some resource types. from UM.Resources import Resources #To find storage paths for some resource types.
@ -15,3 +19,23 @@ class CuraPackageManager(PackageManager):
self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer) self._installation_dirs_dict["qualities"] = Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer)
super().initialize() super().initialize()
## Returns a list of where the package is used
# empty if it is never used.
# It loops through all the package contents and see if some of the ids are used.
# The list consists of 3-tuples: (global_stack, extruder_nr, container_id)
def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]:
ids = self.getPackageContainerIds(package_id)
container_stacks = self._application.getContainerRegistry().findContainerStacks()
global_stacks = [container_stack for container_stack in container_stacks if isinstance(container_stack, GlobalStack)]
machine_with_materials = []
machine_with_qualities = []
for container_id in ids:
for global_stack in global_stacks:
for extruder_nr, extruder_stack in global_stack.extruders.items():
if container_id in (extruder_stack.material.getId(), extruder_stack.material.getMetaData().get("base_file")):
machine_with_materials.append((global_stack, extruder_nr, container_id))
if container_id == extruder_stack.quality.getId():
machine_with_qualities.append((global_stack, extruder_nr, container_id))
return machine_with_materials, machine_with_qualities

View File

@ -1,8 +1,11 @@
# 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 List from typing import List, TYPE_CHECKING
from cura.Machines.MaterialNode import MaterialNode #For type checking.
if TYPE_CHECKING:
from cura.Machines.MaterialNode import MaterialNode
## A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile. ## A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For # The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For
@ -18,11 +21,11 @@ from cura.Machines.MaterialNode import MaterialNode #For type checking.
class MaterialGroup: class MaterialGroup:
__slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list") __slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
def __init__(self, name: str, root_material_node: MaterialNode) -> None: def __init__(self, name: str, root_material_node: "MaterialNode") -> None:
self.name = name self.name = name
self.is_read_only = False self.is_read_only = False
self.root_material_node = root_material_node # type: MaterialNode self.root_material_node = root_material_node # type: MaterialNode
self.derived_material_node_list = [] #type: List[MaterialNode] self.derived_material_node_list = [] # type: List[MaterialNode]
def __str__(self) -> str: def __str__(self) -> str:
return "%s[%s]" % (self.__class__.__name__, self.name) return "%s[%s]" % (self.__class__.__name__, self.name)

View File

@ -378,9 +378,8 @@ class MaterialManager(QObject):
# Look at the guid to material dictionary # Look at the guid to material dictionary
root_material_id = None root_material_id = None
for material_group in self._guid_material_groups_map[material_guid]: for material_group in self._guid_material_groups_map[material_guid]:
if material_group.is_read_only: root_material_id = material_group.root_material_node.metadata["id"]
root_material_id = material_group.root_material_node.metadata["id"] break
break
if not root_material_id: if not root_material_id:
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid) Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)

View File

@ -109,6 +109,10 @@ class BrandMaterialsModel(ListModel):
if brand.lower() == "generic": if brand.lower() == "generic":
continue continue
# Do not include the materials from a to-be-removed package
if bool(metadata.get("removed", False)):
continue
if brand not in brand_group_dict: if brand not in brand_group_dict:
brand_group_dict[brand] = {} brand_group_dict[brand] = {}

View File

@ -41,10 +41,15 @@ class GenericMaterialsModel(BaseMaterialsModel):
item_list = [] item_list = []
for root_material_id, container_node in available_material_dict.items(): for root_material_id, container_node in available_material_dict.items():
metadata = container_node.metadata metadata = container_node.metadata
# Only add results for generic materials # Only add results for generic materials
if metadata["brand"].lower() != "generic": if metadata["brand"].lower() != "generic":
continue continue
# Do not include the materials from a to-be-removed package
if bool(metadata.get("removed", False)):
continue
item = {"root_material_id": root_material_id, item = {"root_material_id": root_material_id,
"id": metadata["id"], "id": metadata["id"],
"name": metadata["name"], "name": metadata["name"],

View File

@ -340,6 +340,13 @@ class QualityManager(QObject):
return quality_group_dict return quality_group_dict
def getDefaultQualityType(self, machine: "GlobalStack") -> Optional[QualityGroup]:
preferred_quality_type = machine.definition.getMetaDataEntry("preferred_quality_type")
quality_group_dict = self.getQualityGroups(machine)
quality_group = quality_group_dict.get(preferred_quality_type)
return quality_group
# #
# Methods for GUI # Methods for GUI
# #
@ -459,18 +466,18 @@ class QualityManager(QObject):
# Create a new quality_changes container for the quality. # Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(new_id) quality_changes = InstanceContainer(new_id)
quality_changes.setName(new_name) quality_changes.setName(new_name)
quality_changes.addMetaDataEntry("type", "quality_changes") quality_changes.setMetaDataEntry("type", "quality_changes")
quality_changes.addMetaDataEntry("quality_type", quality_type) quality_changes.setMetaDataEntry("quality_type", quality_type)
# If we are creating a container for an extruder, ensure we add that to the container # If we are creating a container for an extruder, ensure we add that to the container
if extruder_stack is not None: if extruder_stack is not None:
quality_changes.addMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
# If the machine specifies qualities should be filtered, ensure we match the current criteria. # If the machine specifies qualities should be filtered, ensure we match the current criteria.
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
quality_changes.setDefinition(machine_definition_id) quality_changes.setDefinition(machine_definition_id)
quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion) quality_changes.setMetaDataEntry("setting_version", self._application.SettingVersion)
return quality_changes return quality_changes

View File

@ -369,8 +369,9 @@ class PrintInformation(QObject):
def baseName(self): def baseName(self):
return self._base_name return self._base_name
## Created an acronymn-like abbreviated machine name from the currently active machine name ## Created an acronym-like abbreviated machine name from the currently
# Called each time the global stack is switched # active machine name.
# Called each time the global stack is switched.
def _setAbbreviatedMachineName(self): def _setAbbreviatedMachineName(self):
global_container_stack = self._application.getGlobalContainerStack() global_container_stack = self._application.getGlobalContainerStack()
if not global_container_stack: if not global_container_stack:

View File

@ -5,7 +5,7 @@ import os
import urllib.parse import urllib.parse
import uuid import uuid
from typing import Any from typing import Any
from typing import Dict, Union from typing import Dict, Union, Optional
from PyQt5.QtCore import QObject, QUrl, QVariant from PyQt5.QtCore import QObject, QUrl, QVariant
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
@ -47,13 +47,20 @@ class ContainerManager(QObject):
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]] self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
@pyqtSlot(str, str, result=str) @pyqtSlot(str, str, result=str)
def getContainerMetaDataEntry(self, container_id, entry_name): def getContainerMetaDataEntry(self, container_id: str, entry_names: str) -> str:
metadatas = self._container_registry.findContainersMetadata(id = container_id) metadatas = self._container_registry.findContainersMetadata(id = container_id)
if not metadatas: if not metadatas:
Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
return "" return ""
return str(metadatas[0].get(entry_name, "")) entries = entry_names.split("/")
result = metadatas[0]
while entries:
entry = entries.pop(0)
result = result.get(entry, {})
if not result:
return ""
return str(result)
## Set a metadata entry of the specified container. ## Set a metadata entry of the specified container.
# #
@ -68,6 +75,7 @@ class ContainerManager(QObject):
# #
# \return True if successful, False if not. # \return True if successful, False if not.
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this. # TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
# Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
@pyqtSlot("QVariant", str, str) @pyqtSlot("QVariant", str, str)
def setContainerMetaDataEntry(self, container_node, entry_name, entry_value): def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
root_material_id = container_node.metadata["base_file"] root_material_id = container_node.metadata["base_file"]
@ -102,63 +110,6 @@ class ContainerManager(QObject):
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed. if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
container.metaDataChanged.emit(container) container.metaDataChanged.emit(container)
## Set a setting property of the specified container.
#
# This will set the specified property of the specified setting of the container
# and all containers that share the same base_file (if any). The latter only
# happens for material containers.
#
# \param container_id \type{str} The ID of the container to change.
# \param setting_key \type{str} The key of the setting.
# \param property_name \type{str} The name of the property, eg "value".
# \param property_value \type{str} The new value of the property.
#
# \return True if successful, False if not.
@pyqtSlot(str, str, str, str, result = bool)
def setContainerProperty(self, container_id, setting_key, property_name, property_value):
if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
return False
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could not set properties of container %s because it was not found.", container_id)
return False
container = containers[0]
container.setProperty(setting_key, property_name, property_value)
basefile = container.getMetaDataEntry("base_file", container_id)
for sibbling_container in self._container_registry.findInstanceContainers(base_file = basefile):
if sibbling_container != container:
sibbling_container.setProperty(setting_key, property_name, property_value)
return True
## Get a setting property of the specified container.
#
# This will get the specified property of the specified setting of the
# specified container.
#
# \param container_id The ID of the container to get the setting property
# of.
# \param setting_key The key of the setting to get the property of.
# \param property_name The property to obtain.
# \return The value of the specified property. The type of this property
# value depends on the type of the property. For instance, the "value"
# property of an integer setting will be a Python int, but the "value"
# property of an enum setting will be a Python str.
@pyqtSlot(str, str, str, result = QVariant)
def getContainerProperty(self, container_id: str, setting_key: str, property_name: str):
containers = self._container_registry.findContainers(id = container_id)
if not containers:
Logger.log("w", "Could not get properties of container %s because it was not found.", container_id)
return ""
container = containers[0]
return container.getProperty(setting_key, property_name)
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def makeUniqueName(self, original_name): def makeUniqueName(self, original_name):
return self._container_registry.uniqueName(original_name) return self._container_registry.uniqueName(original_name)

View File

@ -260,11 +260,11 @@ class CuraContainerRegistry(ContainerRegistry):
profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1)) profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id) profile = InstanceContainer(profile_id)
profile.setName(quality_name) profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion) profile.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion)
profile.addMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("type", "quality_changes")
profile.addMetaDataEntry("definition", expected_machine_definition) profile.setMetaDataEntry("definition", expected_machine_definition)
profile.addMetaDataEntry("quality_type", quality_type) profile.setMetaDataEntry("quality_type", quality_type)
profile.addMetaDataEntry("position", "0") profile.setMetaDataEntry("position", "0")
profile.setDirty(True) profile.setDirty(True)
if idx == 0: if idx == 0:
# move all per-extruder settings to the first extruder's quality_changes # move all per-extruder settings to the first extruder's quality_changes
@ -298,7 +298,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_id = machine_extruders[profile_index - 1].definition.getId() extruder_id = machine_extruders[profile_index - 1].definition.getId()
extruder_position = str(profile_index - 1) extruder_position = str(profile_index - 1)
if not profile.getMetaDataEntry("position"): if not profile.getMetaDataEntry("position"):
profile.addMetaDataEntry("position", extruder_position) profile.setMetaDataEntry("position", extruder_position)
else: else:
profile.setMetaDataEntry("position", extruder_position) profile.setMetaDataEntry("position", extruder_position)
profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")
@ -349,7 +349,7 @@ class CuraContainerRegistry(ContainerRegistry):
if "type" in profile.getMetaData(): if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("type", "quality_changes")
else: else:
profile.addMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("type", "quality_changes")
quality_type = profile.getMetaDataEntry("quality_type") quality_type = profile.getMetaDataEntry("quality_type")
if not quality_type: if not quality_type:
@ -480,16 +480,16 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_stack = ExtruderStack.ExtruderStack(unique_name) 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.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
# create a new definition_changes container for the extruder stack # create a new definition_changes container for the extruder stack
definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings" definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings"
definition_changes_name = definition_changes_id definition_changes_name = definition_changes_id
definition_changes = InstanceContainer(definition_changes_id, parent = application) definition_changes = InstanceContainer(definition_changes_id, parent = application)
definition_changes.setName(definition_changes_name) definition_changes.setName(definition_changes_name)
definition_changes.addMetaDataEntry("setting_version", application.SettingVersion) definition_changes.setMetaDataEntry("setting_version", application.SettingVersion)
definition_changes.addMetaDataEntry("type", "definition_changes") definition_changes.setMetaDataEntry("type", "definition_changes")
definition_changes.addMetaDataEntry("definition", extruder_definition.getId()) definition_changes.setMetaDataEntry("definition", extruder_definition.getId())
# move definition_changes settings if exist # move definition_changes settings if exist
for setting_key in definition_changes.getAllKeys(): for setting_key in definition_changes.getAllKeys():
@ -514,9 +514,9 @@ class CuraContainerRegistry(ContainerRegistry):
user_container_name = user_container_id user_container_name = user_container_id
user_container = InstanceContainer(user_container_id, parent = application) user_container = InstanceContainer(user_container_id, parent = application)
user_container.setName(user_container_name) user_container.setName(user_container_name)
user_container.addMetaDataEntry("type", "user") user_container.setMetaDataEntry("type", "user")
user_container.addMetaDataEntry("machine", machine.getId()) user_container.setMetaDataEntry("machine", machine.getId())
user_container.addMetaDataEntry("setting_version", application.SettingVersion) user_container.setMetaDataEntry("setting_version", application.SettingVersion)
user_container.setDefinition(machine.definition.getId()) user_container.setDefinition(machine.definition.getId())
user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
@ -580,7 +580,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName()) extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
if extruder_quality_changes_container: if extruder_quality_changes_container:
quality_changes_id = extruder_quality_changes_container.getId() quality_changes_id = extruder_quality_changes_container.getId()
extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_quality_changes_container.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0] extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
else: else:
# if we still cannot find a quality changes container for the extruder, create a new one # if we still cannot find a quality changes container for the extruder, create a new one
@ -588,10 +588,10 @@ class CuraContainerRegistry(ContainerRegistry):
container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name) container_id = self.uniqueName(extruder_stack.getId() + "_qc_" + container_name)
extruder_quality_changes_container = InstanceContainer(container_id, parent = application) extruder_quality_changes_container = InstanceContainer(container_id, parent = application)
extruder_quality_changes_container.setName(container_name) extruder_quality_changes_container.setName(container_name)
extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes") extruder_quality_changes_container.setMetaDataEntry("type", "quality_changes")
extruder_quality_changes_container.addMetaDataEntry("setting_version", application.SettingVersion) extruder_quality_changes_container.setMetaDataEntry("setting_version", application.SettingVersion)
extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position")) extruder_quality_changes_container.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type")) extruder_quality_changes_container.setMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId()) extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
self.addContainer(extruder_quality_changes_container) self.addContainer(extruder_quality_changes_container)

View File

@ -57,7 +57,7 @@ class CuraContainerStack(ContainerStack):
self.containersChanged.connect(self._onContainersChanged) self.containersChanged.connect(self._onContainersChanged)
import cura.CuraApplication #Here to prevent circular imports. import cura.CuraApplication #Here to prevent circular imports.
self.addMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion) self.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion)
# This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted. # This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted.
pyqtContainersChanged = pyqtSignal() pyqtContainersChanged = pyqtSignal()

View File

@ -146,7 +146,7 @@ class CuraStackBuilder:
stack.setName(extruder_definition.getName()) stack.setName(extruder_definition.getName())
stack.setDefinition(extruder_definition) stack.setDefinition(extruder_definition)
stack.addMetaDataEntry("position", position) stack.setMetaDataEntry("position", position)
user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id, user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
is_global_stack = False) is_global_stack = False)
@ -208,11 +208,11 @@ class CuraStackBuilder:
container = InstanceContainer(unique_container_name) container = InstanceContainer(unique_container_name)
container.setDefinition(definition_id) container.setDefinition(definition_id)
container.addMetaDataEntry("type", "user") container.setMetaDataEntry("type", "user")
container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) container.setMetaDataEntry("setting_version", CuraApplication.SettingVersion)
metadata_key_to_add = "machine" if is_global_stack else "extruder" metadata_key_to_add = "machine" if is_global_stack else "extruder"
container.addMetaDataEntry(metadata_key_to_add, stack_id) container.setMetaDataEntry(metadata_key_to_add, stack_id)
return container return container
@ -226,8 +226,8 @@ class CuraStackBuilder:
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.setMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) definition_changes_container.setMetaDataEntry("setting_version", CuraApplication.SettingVersion)
registry.addContainer(definition_changes_container) registry.addContainer(definition_changes_container)
container_stack.definitionChanges = definition_changes_container container_stack.definitionChanges = definition_changes_container

View File

@ -29,7 +29,7 @@ class ExtruderStack(CuraContainerStack):
def __init__(self, container_id: str) -> None: def __init__(self, container_id: str) -> None:
super().__init__(container_id) super().__init__(container_id)
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility self.setMetaDataEntry("type", "extruder_train") # For backward compatibility
self.propertiesChanged.connect(self._onPropertiesChanged) self.propertiesChanged.connect(self._onPropertiesChanged)
@ -42,7 +42,7 @@ class ExtruderStack(CuraContainerStack):
def setNextStack(self, stack: CuraContainerStack, connect_signals: bool = True) -> None: def setNextStack(self, stack: CuraContainerStack, connect_signals: bool = True) -> None:
super().setNextStack(stack) super().setNextStack(stack)
stack.addExtruder(self) stack.addExtruder(self)
self.addMetaDataEntry("machine", stack.id) self.setMetaDataEntry("machine", stack.id)
# For backward compatibility: Register the extruder with the Extruder Manager # For backward compatibility: Register the extruder with the Extruder Manager
ExtruderManager.getInstance().registerExtruder(self, stack.id) ExtruderManager.getInstance().registerExtruder(self, stack.id)
@ -53,7 +53,7 @@ class ExtruderStack(CuraContainerStack):
def setEnabled(self, enabled: bool) -> None: def setEnabled(self, enabled: bool) -> None:
if "enabled" not in self._metadata: if "enabled" not in self._metadata:
self.addMetaDataEntry("enabled", "True") self.setMetaDataEntry("enabled", "True")
self.setMetaDataEntry("enabled", str(enabled)) self.setMetaDataEntry("enabled", str(enabled))
self.enabledChanged.emit() self.enabledChanged.emit()
@ -138,7 +138,7 @@ class ExtruderStack(CuraContainerStack):
def deserialize(self, contents: str, file_name: Optional[str] = None) -> None: def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
super().deserialize(contents, file_name) super().deserialize(contents, file_name)
if "enabled" not in self.getMetaData(): if "enabled" not in self.getMetaData():
self.addMetaDataEntry("enabled", "True") self.setMetaDataEntry("enabled", "True")
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", "")) stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
if stacks: if stacks:
self.setNextStack(stacks[0]) self.setNextStack(stacks[0])

View File

@ -27,7 +27,7 @@ class GlobalStack(CuraContainerStack):
def __init__(self, container_id: str) -> None: def __init__(self, container_id: str) -> None:
super().__init__(container_id) super().__init__(container_id)
self.addMetaDataEntry("type", "machine") # For backward compatibility self.setMetaDataEntry("type", "machine") # For backward compatibility
self._extruders = {} # type: Dict[str, "ExtruderStack"] self._extruders = {} # type: Dict[str, "ExtruderStack"]

View File

@ -985,6 +985,11 @@ class MachineManager(QObject):
self.updateDefaultExtruder() self.updateDefaultExtruder()
self.updateNumberExtrudersEnabled() self.updateNumberExtrudersEnabled()
self.correctExtruderSettings() self.correctExtruderSettings()
# In case this extruder is being disabled and it's the currently selected one, switch to the default extruder
if not enabled and position == ExtruderManager.getInstance().activeExtruderIndex:
ExtruderManager.getInstance().setActiveExtruderIndex(int(self._default_extruder_position))
# ensure that the quality profile is compatible with current combination, or choose a compatible one if available # ensure that the quality profile is compatible with current combination, or choose a compatible one if available
self._updateQualityWithMaterial() self._updateQualityWithMaterial()
self.extruderChanged.emit() self.extruderChanged.emit()
@ -1299,9 +1304,9 @@ class MachineManager(QObject):
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id) new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
if not new_machine: if not new_machine:
return return
new_machine.addMetaDataEntry("um_network_key", self.activeMachineNetworkKey) new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
new_machine.addMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName) new_machine.setMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
new_machine.addMetaDataEntry("hidden", False) new_machine.setMetaDataEntry("hidden", False)
else: else:
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey) Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey)
new_machine.setMetaDataEntry("hidden", False) new_machine.setMetaDataEntry("hidden", False)
@ -1392,8 +1397,13 @@ class MachineManager(QObject):
material_node = self._material_manager.getMaterialNode(machine_definition_id, variant_name, material_diameter, root_material_id) material_node = self._material_manager.getMaterialNode(machine_definition_id, variant_name, material_diameter, root_material_id)
self.setMaterial(position, material_node) self.setMaterial(position, material_node)
## global_stack: if you want to provide your own global_stack instead of the current active one
# if you update an active machine, special measures have to be taken.
@pyqtSlot(str, "QVariant") @pyqtSlot(str, "QVariant")
def setMaterial(self, position: str, container_node) -> None: def setMaterial(self, position: str, container_node, global_stack: Optional["GlobalStack"] = None) -> None:
if global_stack is not None and global_stack != self._global_container_stack:
global_stack.extruders[position].material = container_node.getContainer()
return
position = str(position) position = str(position)
self.blurSettings.emit() self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
@ -1434,8 +1444,22 @@ class MachineManager(QObject):
quality_group = quality_group_dict[quality_type] quality_group = quality_group_dict[quality_type]
self.setQualityGroup(quality_group) self.setQualityGroup(quality_group)
## Optionally provide global_stack if you want to use your own
# The active global_stack is treated differently.
@pyqtSlot(QObject) @pyqtSlot(QObject)
def setQualityGroup(self, quality_group: QualityGroup, no_dialog: bool = False) -> None: def setQualityGroup(self, quality_group: QualityGroup, no_dialog: bool = False, global_stack: Optional["GlobalStack"] = None) -> None:
if global_stack is not None and global_stack != self._global_container_stack:
if quality_group is None:
Logger.log("e", "Could not set quality group because quality group is None")
return
if quality_group.node_for_global is None:
Logger.log("e", "Could not set quality group [%s] because it has no node_for_global", str(quality_group))
return
global_stack.quality = quality_group.node_for_global.getContainer()
for extruder_nr, extruder_stack in global_stack.extruders.items():
extruder_stack.quality = quality_group.nodes_for_extruders[extruder_nr].getContainer()
return
self.blurSettings.emit() self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._setQualityGroup(quality_group) self._setQualityGroup(quality_group)

View File

@ -37,7 +37,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
self._stack = PerObjectContainerStack(container_id = "per_object_stack_" + str(id(self))) self._stack = PerObjectContainerStack(container_id = "per_object_stack_" + str(id(self)))
self._stack.setDirty(False) # This stack does not need to be saved. self._stack.setDirty(False) # This stack does not need to be saved.
user_container = InstanceContainer(container_id = self._generateUniqueName()) user_container = InstanceContainer(container_id = self._generateUniqueName())
user_container.addMetaDataEntry("type", "user") user_container.setMetaDataEntry("type", "user")
self._stack.userChanges = user_container self._stack.userChanges = user_container
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()

View File

@ -0,0 +1,69 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING, Callable, List
from UM.Logger import Logger
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
#
# This class manages a all registered upon-exit checks that need to be perform when the application tries to exit.
# For example, to show a confirmation dialog when there is USB printing in progress, etc. All callbacks will be called
# in the order of when they got registered. If all callbacks "passes", that is, for example, if the user clicks "yes"
# on the exit confirmation dialog or nothing that's blocking the exit, then the application will quit after that.
#
class OnExitCallbackManager:
def __init__(self, application: "CuraApplication") -> None:
self._application = application
self._on_exit_callback_list = list() # type: List[Callable]
self._current_callback_idx = 0
self._is_all_checks_passed = False
def addCallback(self, callback: Callable) -> None:
self._on_exit_callback_list.append(callback)
Logger.log("d", "on-app-exit callback [%s] added.", callback)
# Reset the current state so the next time it will call all the callbacks again.
def resetCurrentState(self) -> None:
self._current_callback_idx = 0
self._is_all_checks_passed = False
def getIsAllChecksPassed(self) -> bool:
return self._is_all_checks_passed
# Trigger the next callback if available. If not, it means that all callbacks have "passed", which means we should
# not block the application to quit, and it will call the application to actually quit.
def triggerNextCallback(self) -> None:
# Get the next callback and schedule that if
this_callback = None
if self._current_callback_idx < len(self._on_exit_callback_list):
this_callback = self._on_exit_callback_list[self._current_callback_idx]
self._current_callback_idx += 1
if this_callback is not None:
Logger.log("d", "Scheduled the next on-app-exit callback [%s]", this_callback)
self._application.callLater(this_callback)
else:
Logger.log("d", "No more on-app-exit callbacks to process. Tell the app to exit.")
self._is_all_checks_passed = True
# Tell the application to exit
self._application.callLater(self._application.closeApplication)
# This is the callback function which an on-exit callback should call when it finishes, it should provide the
# "should_proceed" flag indicating whether this check has "passed", or in other words, whether quiting the
# application should be blocked. If the last on-exit callback doesn't block the quiting, it will call the next
# registered on-exit callback if available.
def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None:
if not should_proceed:
Logger.log("d", "on-app-exit callback finished and we should not proceed.")
# Reset the state
self.resetCurrentState()
return
self.triggerNextCallback()

View File

View File

@ -12,14 +12,14 @@ from UM.Platform import Platform
parser = argparse.ArgumentParser(prog = "cura", parser = argparse.ArgumentParser(prog = "cura",
add_help = False) add_help = False)
parser.add_argument('--debug', parser.add_argument("--debug",
action='store_true', action="store_true",
default = False, default = False,
help = "Turn on the debug mode by setting this option." help = "Turn on the debug mode by setting this option."
) )
parser.add_argument('--trigger-early-crash', parser.add_argument("--trigger-early-crash",
dest = 'trigger_early_crash', dest = "trigger_early_crash",
action = 'store_true', action = "store_true",
default = False, default = False,
help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog." help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog."
) )
@ -131,6 +131,7 @@ faulthandler.enable(all_threads = True)
# 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 Savitar #@UnusedImport
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
app = CuraApplication() app = CuraApplication()

View File

@ -59,7 +59,7 @@ class ThreeMFReader(MeshReader):
if transformation == "": if transformation == "":
return Matrix() return Matrix()
splitted_transformation = transformation.split() split_transformation = transformation.split()
## Transformation is saved as: ## Transformation is saved as:
## M00 M01 M02 0.0 ## M00 M01 M02 0.0
## M10 M11 M12 0.0 ## M10 M11 M12 0.0
@ -68,20 +68,20 @@ class ThreeMFReader(MeshReader):
## We switch the row & cols as that is how everyone else uses matrices! ## We switch the row & cols as that is how everyone else uses matrices!
temp_mat = Matrix() temp_mat = Matrix()
# Rotation & Scale # Rotation & Scale
temp_mat._data[0, 0] = splitted_transformation[0] temp_mat._data[0, 0] = split_transformation[0]
temp_mat._data[1, 0] = splitted_transformation[1] temp_mat._data[1, 0] = split_transformation[1]
temp_mat._data[2, 0] = splitted_transformation[2] temp_mat._data[2, 0] = split_transformation[2]
temp_mat._data[0, 1] = splitted_transformation[3] temp_mat._data[0, 1] = split_transformation[3]
temp_mat._data[1, 1] = splitted_transformation[4] temp_mat._data[1, 1] = split_transformation[4]
temp_mat._data[2, 1] = splitted_transformation[5] temp_mat._data[2, 1] = split_transformation[5]
temp_mat._data[0, 2] = splitted_transformation[6] temp_mat._data[0, 2] = split_transformation[6]
temp_mat._data[1, 2] = splitted_transformation[7] temp_mat._data[1, 2] = split_transformation[7]
temp_mat._data[2, 2] = splitted_transformation[8] temp_mat._data[2, 2] = split_transformation[8]
# Translation # Translation
temp_mat._data[0, 3] = splitted_transformation[9] temp_mat._data[0, 3] = split_transformation[9]
temp_mat._data[1, 3] = splitted_transformation[10] temp_mat._data[1, 3] = split_transformation[10]
temp_mat._data[2, 3] = splitted_transformation[11] temp_mat._data[2, 3] = split_transformation[11]
return temp_mat return temp_mat
@ -169,8 +169,6 @@ class ThreeMFReader(MeshReader):
archive = zipfile.ZipFile(file_name, "r") archive = zipfile.ZipFile(file_name, "r")
self._base_name = os.path.basename(file_name) self._base_name = os.path.basename(file_name)
parser = Savitar.ThreeMFParser() parser = Savitar.ThreeMFParser()
with open("/tmp/test.xml", "wb") as f:
f.write(archive.open("3D/3dmodel.model").read())
scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read()) scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
self._unit = scene_3mf.getUnit() self._unit = scene_3mf.getUnit()
for node in scene_3mf.getSceneNodes(): for node in scene_3mf.getSceneNodes():

View File

@ -963,7 +963,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if not extruder_info: if not extruder_info:
continue continue
if "enabled" not in extruder_stack.getMetaData(): if "enabled" not in extruder_stack.getMetaData():
extruder_stack.addMetaDataEntry("enabled", "True") extruder_stack.setMetaDataEntry("enabled", "True")
extruder_stack.setMetaDataEntry("enabled", str(extruder_info.enabled)) extruder_stack.setMetaDataEntry("enabled", str(extruder_info.enabled))
def _updateActiveMachine(self, global_stack): def _updateActiveMachine(self, global_stack):

View File

@ -1,4 +1,9 @@
[3.4.1]
*Bug fixes
- Fixed an issue that would occasionally cause an unnecessary extra skin wall to be printed, which increased print time.
- Fixed an issue in which supports were not generated on the initial layer, because the engine expected a brim to be in place.
- Conical and tree supports are now limited within the build plate volume.
- Fixed various startup crashes, including: copying of the version folder, errors while deleting packages, storing the old files, and losing data on install.
[3.4.0] [3.4.0]

View File

@ -21,6 +21,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.SettingInstance import SettingInstance #For typing. from UM.Settings.SettingInstance import SettingInstance #For typing.
from UM.Tool import Tool #For typing. from UM.Tool import Tool #For typing.
from UM.Mesh.MeshData import MeshData #For typing.
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
@ -62,15 +63,15 @@ class CuraEngineBackend(QObject, Backend):
if Platform.isLinux() and not default_engine_location: if Platform.isLinux() and not default_engine_location:
if not os.getenv("PATH"): if not os.getenv("PATH"):
raise OSError("There is something wrong with your Linux installation.") raise OSError("There is something wrong with your Linux installation.")
for pathdir in os.getenv("PATH").split(os.pathsep): for pathdir in cast(str, os.getenv("PATH")).split(os.pathsep):
execpath = os.path.join(pathdir, executable_name) execpath = os.path.join(pathdir, executable_name)
if os.path.exists(execpath): if os.path.exists(execpath):
default_engine_location = execpath default_engine_location = execpath
break break
self._application = CuraApplication.getInstance() #type: CuraApplication self._application = CuraApplication.getInstance() #type: CuraApplication
self._multi_build_plate_model = None #type: MultiBuildPlateModel self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel]
self._machine_error_checker = None #type: MachineErrorChecker self._machine_error_checker = None #type: Optional[MachineErrorChecker]
if not default_engine_location: if not default_engine_location:
raise EnvironmentError("Could not find CuraEngine") raise EnvironmentError("Could not find CuraEngine")
@ -120,7 +121,7 @@ class CuraEngineBackend(QObject, Backend):
self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not? self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not?
self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer
self._error_message = None #type: Message # Pop-up message that shows errors. self._error_message = None #type: Optional[Message] # Pop-up message that shows errors.
self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed
self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool) self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool)
@ -145,7 +146,9 @@ class CuraEngineBackend(QObject, Backend):
self._multi_build_plate_model = self._application.getMultiBuildPlateModel() self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
self._application.getController().activeViewChanged.connect(self._onActiveViewChanged) self._application.getController().activeViewChanged.connect(self._onActiveViewChanged)
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged)
if self._multi_build_plate_model:
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged) self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
@ -246,7 +249,7 @@ class CuraEngineBackend(QObject, Backend):
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate: if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced) self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
if self._process is None: if self._process is None: # type: ignore
self._createSocket() self._createSocket()
self.stopSlicing() self.stopSlicing()
self._engine_is_fresh = False # Yes we're going to use the engine self._engine_is_fresh = False # Yes we're going to use the engine
@ -284,12 +287,12 @@ class CuraEngineBackend(QObject, Backend):
if self._application.getUseExternalBackend(): if self._application.getUseExternalBackend():
return return
if self._process is not None: if self._process is not None: # type: ignore
Logger.log("d", "Killing engine process") Logger.log("d", "Killing engine process")
try: try:
self._process.terminate() self._process.terminate() # type: ignore
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) # type: ignore
self._process = None self._process = None # type: ignore
except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this. except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e)) Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
@ -328,6 +331,9 @@ class CuraEngineBackend(QObject, Backend):
if job.getResult() == StartJobResult.SettingError: if job.getResult() == StartJobResult.SettingError:
if self._application.platformActivity: if self._application.platformActivity:
if not self._global_container_stack:
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
return
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
error_keys = [] #type: List[str] error_keys = [] #type: List[str]
for extruder in extruders: for extruder in extruders:
@ -361,6 +367,9 @@ class CuraEngineBackend(QObject, Backend):
if not stack: if not stack:
continue continue
for key in stack.getErrorKeys(): for key in stack.getErrorKeys():
if not self._global_container_stack:
Logger.log("e", "CuraEngineBackend does not have global_container_stack assigned.")
continue
definition = cast(DefinitionContainerInterface, self._global_container_stack.getBottom()).findDefinitions(key = key) definition = cast(DefinitionContainerInterface, self._global_container_stack.getBottom()).findDefinitions(key = key)
if not definition: if not definition:
Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key)) Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key))
@ -409,7 +418,8 @@ class CuraEngineBackend(QObject, Backend):
# Notify the user that it's now up to the backend to do it's job # Notify the user that it's now up to the backend to do it's job
self.backendStateChange.emit(BackendState.Processing) self.backendStateChange.emit(BackendState.Processing)
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time ) if self._slice_start_time:
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
## Determine enable or disable auto slicing. Return True for enable timer and False otherwise. ## Determine enable or disable auto slicing. Return True for enable timer and False otherwise.
# It disables when # It disables when
@ -447,7 +457,8 @@ class CuraEngineBackend(QObject, Backend):
# Only count sliceable objects # Only count sliceable objects
if node.callDecoration("isSliceable"): if node.callDecoration("isSliceable"):
build_plate_number = node.callDecoration("getBuildPlateNumber") build_plate_number = node.callDecoration("getBuildPlateNumber")
num_objects[build_plate_number] += 1 if build_plate_number is not None:
num_objects[build_plate_number] += 1
return num_objects return num_objects
## Listener for when the scene has changed. ## Listener for when the scene has changed.
@ -476,15 +487,14 @@ class CuraEngineBackend(QObject, Backend):
else: else:
# we got a single scenenode # we got a single scenenode
if not source.callDecoration("isGroup"): if not source.callDecoration("isGroup"):
if source.getMeshData() is None: mesh_data = source.getMeshData()
return if mesh_data is None or mesh_data.getVertices() is None:
if source.getMeshData().getVertices() is None:
return return
build_plate_changed.add(source_build_plate_number) # There are some SceneNodes that do not have any build plate associated, then do not add to the list.
if source_build_plate_number is not None:
build_plate_changed.add(source_build_plate_number)
build_plate_changed.discard(None)
build_plate_changed.discard(-1) # object not on build plate
if not build_plate_changed: if not build_plate_changed:
return return
@ -524,6 +534,11 @@ class CuraEngineBackend(QObject, Backend):
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]: if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
Logger.log("w", "A socket error caused the connection to be reset") Logger.log("w", "A socket error caused the connection to be reset")
# _terminate()' function sets the job status to 'cancel', after reconnecting to another Port the job status
# needs to be updated. Otherwise backendState is "Unable To Slice"
if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
self._start_slice_job.setIsCancelled(False)
## Remove old layer data (if any) ## Remove old layer data (if any)
def _clearLayerData(self, build_plate_numbers: Set = None) -> None: def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
@ -577,9 +592,10 @@ class CuraEngineBackend(QObject, Backend):
# #
# \param message The protobuf message containing sliced layer data. # \param message The protobuf message containing sliced layer data.
def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None: def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None:
if self._start_slice_job_build_plate not in self._stored_optimized_layer_data: if self._start_slice_job_build_plate is not None:
self._stored_optimized_layer_data[self._start_slice_job_build_plate] = [] if self._start_slice_job_build_plate not in self._stored_optimized_layer_data:
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message) self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
## Called when a progress message is received from the engine. ## Called when a progress message is received from the engine.
# #
@ -619,7 +635,8 @@ class CuraEngineBackend(QObject, Backend):
gcode_list[index] = replaced gcode_list[index] = replaced
self._slicing = False self._slicing = False
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time ) if self._slice_start_time:
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate())) Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate()))
# See if we need to process the sliced layers job. # See if we need to process the sliced layers job.
@ -658,7 +675,11 @@ class CuraEngineBackend(QObject, Backend):
## Creates a new socket connection. ## Creates a new socket connection.
def _createSocket(self, protocol_file: str = None) -> None: def _createSocket(self, protocol_file: str = None) -> None:
if not protocol_file: if not protocol_file:
protocol_file = os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")) plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if not plugin_path:
Logger.log("e", "Could not get plugin path!", self.getPluginId())
return
protocol_file = os.path.abspath(os.path.join(plugin_path, "Cura.proto"))
super()._createSocket(protocol_file) super()._createSocket(protocol_file)
self._engine_is_fresh = True self._engine_is_fresh = True
@ -773,9 +794,9 @@ class CuraEngineBackend(QObject, Backend):
# We should reset our state and start listening for new connections. # We should reset our state and start listening for new connections.
def _onBackendQuit(self) -> None: def _onBackendQuit(self) -> None:
if not self._restart: if not self._restart:
if self._process: if self._process: # type: ignore
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait()) Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait()) # type: ignore
self._process = None self._process = None # type: ignore
## Called when the global container stack changes ## Called when the global container stack changes
def _onGlobalStackChanged(self) -> None: def _onGlobalStackChanged(self) -> None:
@ -831,6 +852,9 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.start() self._change_timer.start()
def _extruderChanged(self) -> None: def _extruderChanged(self) -> None:
if not self._multi_build_plate_model:
Logger.log("w", "CuraEngineBackend does not have multi_build_plate_model assigned!")
return
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1): for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
if build_plate_number not in self._build_plates_to_be_sliced: if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number) self._build_plates_to_be_sliced.append(build_plate_number)

View File

@ -5,7 +5,7 @@ import numpy
from string import Formatter from string import Formatter
from enum import IntEnum from enum import IntEnum
import time import time
from typing import Any, Dict, List, Optional, Set from typing import Any, cast, Dict, List, Optional, Set
import re import re
import Arcus #For typing. import Arcus #For typing.
@ -41,39 +41,32 @@ class StartJobResult(IntEnum):
## Formatter class that handles token expansion in start/end gcode ## Formatter class that handles token expansion in start/end gcode
class GcodeStartEndFormatter(Formatter): class GcodeStartEndFormatter(Formatter):
def get_value(self, key: str, *args: str, **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] def get_value(self, key: str, *args: str, default_extruder_nr: str = "-1", **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key), # The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
# and a default_extruder_nr to use when no extruder_nr is specified # and a default_extruder_nr to use when no extruder_nr is specified
if isinstance(key, str): extruder_nr = int(default_extruder_nr)
key_fragments = [fragment.strip() for fragment in key.split(",")]
if len(key_fragments) == 2:
try: try:
extruder_nr = int(kwargs["default_extruder_nr"]) extruder_nr = int(key_fragments[1])
except ValueError: except ValueError:
extruder_nr = -1
key_fragments = [fragment.strip() for fragment in key.split(",")]
if len(key_fragments) == 2:
try: try:
extruder_nr = int(key_fragments[1]) extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack #TODO: How can you ever provide the '-1' kwarg?
except ValueError: except (KeyError, ValueError):
try: # either the key does not exist, or the value is not an int
extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack Logger.log("w", "Unable to determine stack nr '%s' for key '%s' in start/end g-code, using global stack", key_fragments[1], key_fragments[0])
except (KeyError, ValueError): elif len(key_fragments) != 1:
# either the key does not exist, or the value is not an int
Logger.log("w", "Unable to determine stack nr '%s' for key '%s' in start/end g-code, using global stack", key_fragments[1], key_fragments[0])
elif len(key_fragments) != 1:
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key)
return "{" + str(key) + "}"
key = key_fragments[0]
try:
return kwargs[str(extruder_nr)][key]
except KeyError:
Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key)
return "{" + key + "}"
else:
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key) Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key)
return "{" + str(key) + "}" return "{" + key + "}"
key = key_fragments[0]
try:
return kwargs[str(extruder_nr)][key]
except KeyError:
Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key)
return "{" + key + "}"
## Job class that builds up the message of scene data to send to CuraEngine. ## Job class that builds up the message of scene data to send to CuraEngine.
@ -216,12 +209,15 @@ class StartSliceJob(Job):
if temp_list: if temp_list:
object_groups.append(temp_list) object_groups.append(temp_list)
extruders_enabled = {position: stack.isEnabled for position, stack in CuraApplication.getInstance().getGlobalContainerStack().extruders.items()} global_stack = CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack:
return
extruders_enabled = {position: stack.isEnabled for position, stack in global_stack.extruders.items()}
filtered_object_groups = [] filtered_object_groups = []
has_model_with_disabled_extruders = False has_model_with_disabled_extruders = False
associated_disabled_extruders = set() associated_disabled_extruders = set()
for group in object_groups: for group in object_groups:
stack = CuraApplication.getInstance().getGlobalContainerStack() stack = global_stack
skip_group = False skip_group = False
for node in group: for node in group:
extruder_position = node.callDecoration("getActiveExtruderPosition") extruder_position = node.callDecoration("getActiveExtruderPosition")
@ -234,7 +230,7 @@ class StartSliceJob(Job):
if has_model_with_disabled_extruders: if has_model_with_disabled_extruders:
self.setResult(StartJobResult.ObjectsWithDisabledExtruder) self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
associated_disabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])] associated_disabled_extruders = {str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])}
self.setMessage(", ".join(associated_disabled_extruders)) self.setMessage(", ".join(associated_disabled_extruders))
return return
@ -294,6 +290,9 @@ class StartSliceJob(Job):
def isCancelled(self) -> bool: def isCancelled(self) -> bool:
return self._is_cancelled return self._is_cancelled
def setIsCancelled(self, value: bool):
self._is_cancelled = value
## Creates a dictionary of tokens to replace in g-code pieces. ## Creates a dictionary of tokens to replace in g-code pieces.
# #
# This indicates what should be replaced in the start and end g-codes. # This indicates what should be replaced in the start and end g-codes.
@ -325,7 +324,7 @@ class StartSliceJob(Job):
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack # \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
if not self._all_extruders_settings: if not self._all_extruders_settings:
global_stack = CuraApplication.getInstance().getGlobalContainerStack() global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack())
# NB: keys must be strings for the string formatter # NB: keys must be strings for the string formatter
self._all_extruders_settings = { self._all_extruders_settings = {

View File

@ -75,7 +75,7 @@ class CuraProfileReader(ProfileReader):
def _loadProfile(self, serialized, profile_id): def _loadProfile(self, serialized, profile_id):
# Create an empty profile. # Create an empty profile.
profile = InstanceContainer(profile_id) profile = InstanceContainer(profile_id)
profile.addMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("type", "quality_changes")
try: try:
profile.deserialize(serialized) profile.deserialize(serialized)
except ContainerFormatError as e: except ContainerFormatError as e:

View File

@ -286,6 +286,10 @@ class FlavorParser:
self._cancelled = False self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths # We obtain the filament diameter from the selected extruder to calculate line widths
global_stack = CuraApplication.getInstance().getGlobalContainerStack() global_stack = CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack:
return None
self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value") self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value")
scene_node = CuraSceneNode() scene_node = CuraSceneNode()

View File

@ -127,11 +127,11 @@ class GCodeWriter(MeshWriter):
flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile) flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
# If the quality changes is not set, we need to set type manually # If the quality changes is not set, we need to set type manually
if flat_global_container.getMetaDataEntry("type", None) is None: if flat_global_container.getMetaDataEntry("type", None) is None:
flat_global_container.addMetaDataEntry("type", "quality_changes") flat_global_container.setMetaDataEntry("type", "quality_changes")
# Ensure that quality_type is set. (Can happen if we have empty quality changes). # Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_global_container.getMetaDataEntry("quality_type", None) is None: if flat_global_container.getMetaDataEntry("quality_type", None) is None:
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal")) flat_global_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
# Get the machine definition ID for quality profiles # Get the machine definition ID for quality profiles
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition) machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
@ -151,15 +151,15 @@ class GCodeWriter(MeshWriter):
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality) flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
# If the quality changes is not set, we need to set type manually # If the quality changes is not set, we need to set type manually
if flat_extruder_quality.getMetaDataEntry("type", None) is None: if flat_extruder_quality.getMetaDataEntry("type", None) is None:
flat_extruder_quality.addMetaDataEntry("type", "quality_changes") flat_extruder_quality.setMetaDataEntry("type", "quality_changes")
# Ensure that extruder is set. (Can happen if we have empty quality changes). # Ensure that extruder is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("position", None) is None: if flat_extruder_quality.getMetaDataEntry("position", None) is None:
flat_extruder_quality.addMetaDataEntry("position", extruder.getMetaDataEntry("position")) flat_extruder_quality.setMetaDataEntry("position", extruder.getMetaDataEntry("position"))
# Ensure that quality_type is set. (Can happen if we have empty quality changes). # Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None: if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal")) flat_extruder_quality.setMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
# Change the default definition # Change the default definition
flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality) flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality)

View File

@ -145,9 +145,9 @@ class LegacyProfileReader(ProfileReader):
if len(profile.getAllKeys()) == 0: if len(profile.getAllKeys()) == 0:
Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.") Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.")
profile.addMetaDataEntry("type", "profile") profile.setMetaDataEntry("type", "profile")
# don't know what quality_type it is based on, so use "normal" by default # don't know what quality_type it is based on, so use "normal" by default
profile.addMetaDataEntry("quality_type", "normal") profile.setMetaDataEntry("quality_type", "normal")
profile.setName(profile_id) profile.setName(profile_id)
profile.setDirty(True) profile.setDirty(True)

View File

@ -135,10 +135,7 @@ class MachineSettingsAction(MachineAction):
material_node = None material_node = None
if has_materials: if has_materials:
if "has_materials" in self._global_container_stack.getMetaData(): self._global_container_stack.setMetaDataEntry("has_materials", True)
self._global_container_stack.setMetaDataEntry("has_materials", True)
else:
self._global_container_stack.addMetaDataEntry("has_materials", True)
else: else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only. # The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.

View File

@ -248,7 +248,7 @@ class PostProcessingPlugin(QObject, Extension):
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
if "post_processing_scripts" not in global_stack.getMetaData(): if "post_processing_scripts" not in global_stack.getMetaData():
global_stack.addMetaDataEntry("post_processing_scripts", "") global_stack.setMetaDataEntry("post_processing_scripts", "")
Application.getInstance().getGlobalContainerStack().setMetaDataEntry("post_processing_scripts", script_list_strs) Application.getInstance().getGlobalContainerStack().setMetaDataEntry("post_processing_scripts", script_list_strs)
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection. ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.

View File

@ -48,7 +48,7 @@ class Script:
self._stack.addContainer(self._definition) self._stack.addContainer(self._definition)
self._instance = InstanceContainer(container_id="ScriptInstanceContainer") self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
self._instance.setDefinition(self._definition.getId()) self._instance.setDefinition(self._definition.getId())
self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0)) self._instance.setMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0))
self._stack.addContainer(self._instance) self._stack.addContainer(self._instance)
self._stack.propertyChanged.connect(self._onPropertyChanged) self._stack.propertyChanged.connect(self._onPropertyChanged)
@ -105,9 +105,12 @@ class Script:
if m is None: if m is None:
return default return default
try: try:
return float(m.group(0)) return int(m.group(0))
except: except ValueError: #Not an integer.
return default try:
return float(m.group(0))
except ValueError: #Not a number at all.
return default
## Convenience function to produce a line of g-code. ## Convenience function to produce a line of g-code.
# #

View File

@ -58,10 +58,10 @@ class FilamentChange(Script):
color_change = "M600" color_change = "M600"
if initial_retract is not None and initial_retract > 0.: if initial_retract is not None and initial_retract > 0.:
color_change = color_change + (" E%.2f" % initial_retract) color_change = color_change + (" E-%.2f" % initial_retract)
if later_retract is not None and later_retract > 0.: if later_retract is not None and later_retract > 0.:
color_change = color_change + (" L%.2f" % later_retract) color_change = color_change + (" L-%.2f" % later_retract)
color_change = color_change + " ; Generated by FilamentChange plugin" color_change = color_change + " ; Generated by FilamentChange plugin"

View File

@ -1,5 +1,9 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from ..Script import Script from ..Script import Script
# from cura.Settings.ExtruderManager import ExtruderManager
from UM.Application import Application #To get the current printer's settings.
class PauseAtHeight(Script): class PauseAtHeight(Script):
def __init__(self): def __init__(self):
@ -136,6 +140,10 @@ class PauseAtHeight(Script):
layers_started = False layers_started = False
redo_layers = self.getSettingValueByKey("redo_layers") redo_layers = self.getSettingValueByKey("redo_layers")
standby_temperature = self.getSettingValueByKey("standby_temperature") standby_temperature = self.getSettingValueByKey("standby_temperature")
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
is_griffin = False
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value") # T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
@ -153,6 +161,8 @@ class PauseAtHeight(Script):
# Scroll each line of instruction for each layer in the G-code # Scroll each line of instruction for each layer in the G-code
for line in lines: for line in lines:
if ";FLAVOR:Griffin" in line:
is_griffin = True
# Fist positive layer reached # Fist positive layer reached
if ";LAYER:0" in line: if ";LAYER:0" in line:
layers_started = True layers_started = True
@ -162,12 +172,12 @@ class PauseAtHeight(Script):
#Track the latest printing temperature in order to resume at the correct temperature. #Track the latest printing temperature in order to resume at the correct temperature.
if line.startswith("T"): if line.startswith("T"):
current_t = int(self.getValue(line, "T")) current_t = self.getValue(line, "T")
m = self.getValue(line, "M") m = self.getValue(line, "M")
if m is not None and (int(m) == 104 or int(m) == 109) and self.getValue(line, "S") is not None: if m is not None and (m == 104 or m == 109) and self.getValue(line, "S") is not None:
extruder = current_t extruder = current_t
if self.getValue(line, "T") is not None: if self.getValue(line, "T") is not None:
extruder = int(self.getValue(line, "T")) extruder = self.getValue(line, "T")
target_temperature[extruder] = self.getValue(line, "S") target_temperature[extruder] = self.getValue(line, "S")
if not layers_started: if not layers_started:
@ -247,57 +257,71 @@ class PauseAtHeight(Script):
prepend_gcode += ";added code by post processing\n" prepend_gcode += ";added code by post processing\n"
prepend_gcode += ";script: PauseAtHeight.py\n" prepend_gcode += ";script: PauseAtHeight.py\n"
if pause_at == "height": if pause_at == "height":
prepend_gcode += ";current z: {z}\n".format(z=current_z) prepend_gcode += ";current z: {z}\n".format(z = current_z)
prepend_gcode += ";current height: {height}\n".format(height=current_height) prepend_gcode += ";current height: {height}\n".format(height = current_height)
else: else:
prepend_gcode += ";current layer: {layer}\n".format(layer=current_layer) prepend_gcode += ";current layer: {layer}\n".format(layer = current_layer)
# Retraction if not is_griffin:
prepend_gcode += self.putValue(M=83) + "\n" # Retraction
if retraction_amount != 0: prepend_gcode += self.putValue(M = 83) + "\n"
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n" if retraction_amount != 0:
if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves.
retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature.
for i in range(retraction_count):
prepend_gcode += self.putValue(G = 10) + "\n"
else:
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head away # Move the head away
prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n" prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
# This line should be ok # This line should be ok
prepend_gcode += self.putValue(G=1, X=park_x, Y=park_y, F=9000) + "\n" prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n"
if current_z < 15: if current_z < 15:
prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n" prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n"
# Set extruder standby temperature if control_temperatures:
prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n" # Set extruder standby temperature
prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n"
# Wait till the user continues printing # Wait till the user continues printing
prepend_gcode += self.putValue(M=0) + ";Do the actual pause\n" prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n"
# Set extruder resume temperature if not is_griffin:
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n" if control_temperatures:
# Set extruder resume temperature
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n"
# Push the filament back, # Push the filament back,
if retraction_amount != 0: if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n" prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
# Optionally extrude material # Optionally extrude material
if extrude_amount != 0: if extrude_amount != 0:
prepend_gcode += self.putValue(G=1, E=extrude_amount, F=extrude_speed * 60) + "\n" prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n"
# and retract again, the properly primes the nozzle # and retract again, the properly primes the nozzle
# when changing filament. # when changing filament.
if retraction_amount != 0: if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n" prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head back # Move the head back
prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n" prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
prepend_gcode += self.putValue(G=1, X=x, Y=y, F=9000) + "\n" prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n"
if retraction_amount != 0: if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n" if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves.
prepend_gcode += self.putValue(G=1, F=9000) + "\n" retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature.
prepend_gcode += self.putValue(M=82) + "\n" for i in range(retraction_count):
prepend_gcode += self.putValue(G = 11) + "\n"
else:
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
prepend_gcode += self.putValue(G = 1, F = 9000) + "\n"
prepend_gcode += self.putValue(M = 82) + "\n"
# reset extrude value to pre pause value # reset extrude value to pre pause value
prepend_gcode += self.putValue(G=92, E=current_e) + "\n" prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
layer = prepend_gcode + layer layer = prepend_gcode + layer

View File

@ -35,25 +35,40 @@ class GCodeStep():
Class to store the current value of each G_Code parameter Class to store the current value of each G_Code parameter
for any G-Code step for any G-Code step
""" """
def __init__(self, step): def __init__(self, step, in_relative_movement: bool = False):
self.step = step self.step = step
self.step_x = 0 self.step_x = 0
self.step_y = 0 self.step_y = 0
self.step_z = 0 self.step_z = 0
self.step_e = 0 self.step_e = 0
self.step_f = 0 self.step_f = 0
self.in_relative_movement = in_relative_movement
self.comment = "" self.comment = ""
def readStep(self, line): def readStep(self, line):
""" """
Reads gcode from line into self Reads gcode from line into self
""" """
self.step_x = _getValue(line, "X", self.step_x) if not self.in_relative_movement:
self.step_y = _getValue(line, "Y", self.step_y) self.step_x = _getValue(line, "X", self.step_x)
self.step_z = _getValue(line, "Z", self.step_z) self.step_y = _getValue(line, "Y", self.step_y)
self.step_e = _getValue(line, "E", self.step_e) self.step_z = _getValue(line, "Z", self.step_z)
self.step_f = _getValue(line, "F", self.step_f) self.step_e = _getValue(line, "E", self.step_e)
return self.step_f = _getValue(line, "F", self.step_f)
else:
delta_step_x = _getValue(line, "X", 0)
delta_step_y = _getValue(line, "Y", 0)
delta_step_z = _getValue(line, "Z", 0)
delta_step_e = _getValue(line, "E", 0)
delta_step_f = _getValue(line, "F", 0)
self.step_x += delta_step_x
self.step_y += delta_step_y
self.step_z += delta_step_z
self.step_e += delta_step_e
self.step_f += delta_step_f
def copyPosFrom(self, step): def copyPosFrom(self, step):
""" """
@ -65,7 +80,9 @@ class GCodeStep():
self.step_e = step.step_e self.step_e = step.step_e
self.step_f = step.step_f self.step_f = step.step_f
self.comment = step.comment self.comment = step.comment
return
def setInRelativeMovement(self, value: bool) -> None:
self.in_relative_movement = value
# Execution part of the stretch plugin # Execution part of the stretch plugin
@ -86,6 +103,7 @@ class Stretcher():
# of already deposited material for current layer # of already deposited material for current layer
self.layer_z = 0 # Z position of the extrusion moves of the current layer self.layer_z = 0 # Z position of the extrusion moves of the current layer
self.layergcode = "" self.layergcode = ""
self._in_relative_movement = False
def execute(self, data): def execute(self, data):
""" """
@ -96,7 +114,8 @@ class Stretcher():
+ " and push wall stretch " + str(self.pw_stretch) + "mm") + " and push wall stretch " + str(self.pw_stretch) + "mm")
retdata = [] retdata = []
layer_steps = [] layer_steps = []
current = GCodeStep(0) in_relative_movement = False
current = GCodeStep(0, in_relative_movement)
self.layer_z = 0. self.layer_z = 0.
current_e = 0. current_e = 0.
for layer in data: for layer in data:
@ -107,20 +126,31 @@ class Stretcher():
current.comment = line[line.find(";"):] current.comment = line[line.find(";"):]
if _getValue(line, "G") == 0: if _getValue(line, "G") == 0:
current.readStep(line) current.readStep(line)
onestep = GCodeStep(0) onestep = GCodeStep(0, in_relative_movement)
onestep.copyPosFrom(current) onestep.copyPosFrom(current)
elif _getValue(line, "G") == 1: elif _getValue(line, "G") == 1:
current.readStep(line) current.readStep(line)
onestep = GCodeStep(1) onestep = GCodeStep(1, in_relative_movement)
onestep.copyPosFrom(current) onestep.copyPosFrom(current)
# end of relative movement
elif _getValue(line, "G") == 90:
in_relative_movement = False
current.setInRelativeMovement(in_relative_movement)
# start of relative movement
elif _getValue(line, "G") == 91:
in_relative_movement = True
current.setInRelativeMovement(in_relative_movement)
elif _getValue(line, "G") == 92: elif _getValue(line, "G") == 92:
current.readStep(line) current.readStep(line)
onestep = GCodeStep(-1) onestep = GCodeStep(-1, in_relative_movement)
onestep.copyPosFrom(current) onestep.copyPosFrom(current)
else: else:
onestep = GCodeStep(-1) onestep = GCodeStep(-1, in_relative_movement)
onestep.copyPosFrom(current) onestep.copyPosFrom(current)
onestep.comment = line onestep.comment = line
if line.find(";LAYER:") >= 0 and len(layer_steps): if line.find(";LAYER:") >= 0 and len(layer_steps):
# Previous plugin "forgot" to separate two layers... # Previous plugin "forgot" to separate two layers...
Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z) Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z)

View File

@ -11,7 +11,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
# Ignore windows error popups. Fixes the whole "Can't open drive X" when user has an SD card reader. # Ignore windows error popups. Fixes the whole "Can't open drive X" when user has an SD card reader.
ctypes.windll.kernel32.SetErrorMode(1) ctypes.windll.kernel32.SetErrorMode(1) #type: ignore
# WinAPI Constants that we need # WinAPI Constants that we need
# Hardcoded here due to stupid WinDLL stuff that does not give us access to these values. # Hardcoded here due to stupid WinDLL stuff that does not give us access to these values.
@ -29,7 +29,7 @@ OPEN_EXISTING = 3 # [CodeStyle: Windows Enum value]
# Setup the DeviceIoControl function arguments and return type. # Setup the DeviceIoControl function arguments and return type.
# See ctypes documentation for details on how to call C functions from python, and why this is important. # See ctypes documentation for details on how to call C functions from python, and why this is important.
ctypes.windll.kernel32.DeviceIoControl.argtypes = [ ctypes.windll.kernel32.DeviceIoControl.argtypes = [ #type: ignore
wintypes.HANDLE, # _In_ HANDLE hDevice wintypes.HANDLE, # _In_ HANDLE hDevice
wintypes.DWORD, # _In_ DWORD dwIoControlCode wintypes.DWORD, # _In_ DWORD dwIoControlCode
wintypes.LPVOID, # _In_opt_ LPVOID lpInBuffer wintypes.LPVOID, # _In_opt_ LPVOID lpInBuffer
@ -39,7 +39,7 @@ ctypes.windll.kernel32.DeviceIoControl.argtypes = [
ctypes.POINTER(wintypes.DWORD), # _Out_opt_ LPDWORD lpBytesReturned ctypes.POINTER(wintypes.DWORD), # _Out_opt_ LPDWORD lpBytesReturned
wintypes.LPVOID # _Inout_opt_ LPOVERLAPPED lpOverlapped wintypes.LPVOID # _Inout_opt_ LPOVERLAPPED lpOverlapped
] ]
ctypes.windll.kernel32.DeviceIoControl.restype = wintypes.BOOL ctypes.windll.kernel32.DeviceIoControl.restype = wintypes.BOOL #type: ignore
## Removable drive support for windows ## Removable drive support for windows

View File

@ -16,7 +16,7 @@ from UM.i18n import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
from typing import cast, Optional
from .SliceInfoJob import SliceInfoJob from .SliceInfoJob import SliceInfoJob
@ -79,11 +79,16 @@ class SliceInfo(QObject, Extension):
return dialog return dialog
@pyqtSlot(result = str) @pyqtSlot(result = str)
def getExampleData(self) -> str: def getExampleData(self) -> Optional[str]:
if self._example_data_content is None: if self._example_data_content is None:
file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "example_data.json") plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
with open(file_path, "r", encoding = "utf-8") as f: if not plugin_path:
self._example_data_content = f.read() Logger.log("e", "Could not get plugin path!", self.getPluginId())
return None
file_path = os.path.join(plugin_path, "example_data.json")
if file_path:
with open(file_path, "r", encoding = "utf-8") as f:
self._example_data_content = f.read()
return self._example_data_content return self._example_data_content
@pyqtSlot(bool) @pyqtSlot(bool)

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path d="M0,512h512V0L0,512z M440.4,318.3L331.2,431.6c-1.4,1.4-2.7,2-4.8,2c-2,0-3.4-0.7-4.8-2l-53.3-57.3l-1.4-2
c-1.4-1.4-2-3.4-2-4.8c0-1.4,0.7-3.4,2-4.8l9.6-9.6c2.7-2.7,6.8-2.7,9.6,0l0.7,0.7l37.6,40.2c1.4,1.4,3.4,1.4,4.8,0l91.4-94.9h0.7
c2.7-2.7,6.8-2.7,9.6,0l9.5,9.6C443.1,311.5,443.1,315.6,440.4,318.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 667 B

View File

@ -76,11 +76,26 @@ Item
} }
} }
Component
{
id: columnTextDelegate
Label
{
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: styleData.value || ""
elide: Text.ElideRight
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
}
}
TableViewColumn TableViewColumn
{ {
role: "machine" role: "machine"
title: "Machine" title: "Machine"
width: Math.floor(table.width * 0.25) width: Math.floor(table.width * 0.25)
delegate: columnTextDelegate
} }
TableViewColumn TableViewColumn
{ {

View File

@ -0,0 +1,95 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.1
import UM 1.3 as UM
import Cura 1.0 as Cura
UM.Dialog
{
// This dialog asks the user whether he/she wants to open a project file as a project or import models.
id: base
title: catalog.i18nc("@title:window", "Confirm uninstall ") + toolbox.pluginToUninstall
width: 450 * screenScaleFactor
height: 50 * screenScaleFactor + dialogText.height + buttonBar.height
maximumWidth: 450 * screenScaleFactor
maximumHeight: 450 * screenScaleFactor
minimumWidth: 450 * screenScaleFactor
minimumHeight: 150 * screenScaleFactor
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal
Column
{
UM.I18nCatalog { id: catalog; name: "cura" }
anchors
{
fill: parent
leftMargin: Math.round(20 * screenScaleFactor)
rightMargin: Math.round(20 * screenScaleFactor)
topMargin: Math.round(10 * screenScaleFactor)
bottomMargin: Math.round(10 * screenScaleFactor)
}
spacing: Math.round(15 * screenScaleFactor)
Label
{
id: dialogText
text:
{
var base_text = catalog.i18nc("@text:window", "You are uninstalling materials and/or profiles that are still in use. Confirming will reset the following materials/profiles to their defaults.")
var materials_text = catalog.i18nc("@text:window", "Materials")
var qualities_text = catalog.i18nc("@text:window", "Profiles")
var machines_with_materials = toolbox.uninstallUsedMaterials
var machines_with_qualities = toolbox.uninstallUsedQualities
if (machines_with_materials != "")
{
base_text += "\n\n" + materials_text +": \n" + machines_with_materials
}
if (machines_with_qualities != "")
{
base_text += "\n\n" + qualities_text + ": \n" + machines_with_qualities
}
return base_text
}
anchors.left: parent.left
anchors.right: parent.right
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
}
// Buttons
Item {
id: buttonBar
anchors.right: parent.right
anchors.left: parent.left
height: childrenRect.height
Button {
id: cancelButton
text: catalog.i18nc("@action:button", "Cancel")
anchors.right: confirmButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
isDefault: true
onClicked: toolbox.closeConfirmResetDialog()
}
Button {
id: confirmButton
text: catalog.i18nc("@action:button", "Confirm")
anchors.right: parent.right
onClicked: toolbox.resetMaterialsQualitiesAndUninstall()
}
}
}
}

View File

@ -74,6 +74,7 @@ Item
} }
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height) spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
width: childrenRect.width width: childrenRect.width
height: childrenRect.height
Label Label
{ {
text: catalog.i18nc("@label", "Version") + ":" text: catalog.i18nc("@label", "Version") + ":"
@ -110,6 +111,7 @@ Item
topMargin: UM.Theme.getSize("default_margin").height topMargin: UM.Theme.getSize("default_margin").height
} }
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height) spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
height: childrenRect.height
Label Label
{ {
text: details.version || catalog.i18nc("@label", "Unknown") text: details.version || catalog.i18nc("@label", "Unknown")

View File

@ -9,6 +9,9 @@ import UM 1.1 as UM
Column Column
{ {
property var heading: ""
property var model
id: gridArea
height: childrenRect.height + 2 * padding height: childrenRect.height + 2 * padding
width: parent.width width: parent.width
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
@ -16,7 +19,7 @@ Column
Label Label
{ {
id: heading id: heading
text: toolbox.viewCategory == "material" ? catalog.i18nc("@label", "Community contributions") : catalog.i18nc("@label", "Community plugins") text: gridArea.heading
width: parent.width width: parent.width
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
@ -24,14 +27,13 @@ Column
GridLayout GridLayout
{ {
id: grid id: grid
property var model: toolbox.viewCategory == "material" ? toolbox.authorsModel : toolbox.packagesModel width: parent.width - 2 * parent.padding
width: parent.width
columns: 2 columns: 2
columnSpacing: UM.Theme.getSize("default_margin").height columnSpacing: UM.Theme.getSize("default_margin").height
rowSpacing: UM.Theme.getSize("default_margin").width rowSpacing: UM.Theme.getSize("default_margin").width
Repeater Repeater
{ {
model: grid.model model: gridArea.model
delegate: ToolboxDownloadsGridTile delegate: ToolboxDownloadsGridTile
{ {
Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns Layout.preferredWidth: (grid.width - (grid.columns - 1) * grid.columnSpacing) / grid.columns

View File

@ -9,6 +9,8 @@ import UM 1.1 as UM
Item Item
{ {
property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
height: childrenRect.height height: childrenRect.height
Layout.alignment: Qt.AlignTop | Qt.AlignLeft Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Rectangle Rectangle
@ -40,6 +42,21 @@ Item
source: model.icon_url || "../images/logobot.svg" source: model.icon_url || "../images/logobot.svg"
mipmap: true mipmap: true
} }
UM.RecolorImage
{
width: (parent.width * 0.4) | 0
height: (parent.height * 0.4) | 0
anchors
{
bottom: parent.bottom
right: parent.right
}
sourceSize.width: width
sourceSize.height: height
visible: installedPackages != 0
color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
source: "../images/installed_check.svg"
}
} }
Column Column
{ {
@ -87,8 +104,18 @@ Item
switch(toolbox.viewCategory) switch(toolbox.viewCategory)
{ {
case "material": case "material":
toolbox.viewPage = "author"
toolbox.filterModelByProp("packages", "author_id", model.id) // If model has a type, it must be a package
if (model.type !== undefined)
{
toolbox.viewPage = "detail"
toolbox.filterModelByProp("packages", "id", model.id)
}
else
{
toolbox.viewPage = "author"
toolbox.filterModelByProp("packages", "author_id", model.id)
}
break break
default: default:
toolbox.viewPage = "detail" toolbox.viewPage = "detail"

View File

@ -29,6 +29,17 @@ ScrollView
{ {
id: allPlugins id: allPlugins
width: parent.width width: parent.width
heading: toolbox.viewCategory == "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins")
model: toolbox.viewCategory == "material" ? toolbox.authorsModel : toolbox.packagesModel
}
ToolboxDownloadsGrid
{
id: genericMaterials
visible: toolbox.viewCategory == "material"
width: parent.width
heading: catalog.i18nc("@label", "Generic Materials")
model: toolbox.materialsGenericModel
} }
} }
} }

View File

@ -9,6 +9,8 @@ import UM 1.1 as UM
Rectangle Rectangle
{ {
property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
property int installedPackages: toolbox.viewCategory == "material" ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
id: tileBase id: tileBase
width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width) width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width)
height: thumbnail.height + packageNameBackground.height + (2 * UM.Theme.getSize("default_lining").width) height: thumbnail.height + packageNameBackground.height + (2 * UM.Theme.getSize("default_lining").width)
@ -36,6 +38,22 @@ Rectangle
source: model.icon_url || "../images/logobot.svg" source: model.icon_url || "../images/logobot.svg"
mipmap: true mipmap: true
} }
UM.RecolorImage
{
width: (parent.width * 0.3) | 0
height: (parent.height * 0.3) | 0
anchors
{
bottom: parent.bottom
right: parent.right
bottomMargin: UM.Theme.getSize("default_lining").width
}
sourceSize.width: width
sourceSize.height: height
visible: installedPackages != 0
color: (installedPackages == packageCount) ? UM.Theme.getColor("primary") : UM.Theme.getColor("border")
source: "../images/installed_check.svg"
}
} }
Rectangle Rectangle
{ {

View File

@ -15,6 +15,7 @@ Item
Label Label
{ {
text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.") text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.")
color: UM.Theme.getColor("text")
height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height) height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height)
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
anchors anchors
@ -25,6 +26,7 @@ Item
right: restartButton.right right: restartButton.right
rightMargin: UM.Theme.getSize("default_margin").width rightMargin: UM.Theme.getSize("default_margin").width
} }
} }
Button Button
{ {

View File

@ -83,7 +83,7 @@ Column
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
} }
} }
onClicked: toolbox.uninstall(model.id) onClicked: toolbox.checkPackageUsageAndUninstall(model.id)
Connections Connections
{ {
target: toolbox target: toolbox

View File

@ -43,7 +43,7 @@ class AuthorsModel(ListModel):
"package_count": author["package_count"] if "package_count" in author else 0, "package_count": author["package_count"] if "package_count" in author else 0,
"package_types": author["package_types"] if "package_types" in author else [], "package_types": author["package_types"] if "package_types" in author else [],
"icon_url": author["icon_url"] if "icon_url" in author else None, "icon_url": author["icon_url"] if "icon_url" in author else None,
"description": "Material and quality profiles from {author_name}".format( author_name = author["display_name"]) "description": "Material and quality profiles from {author_name}".format(author_name = author["display_name"])
}) })
# Filter on all the key-word arguments. # Filter on all the key-word arguments.

View File

@ -33,6 +33,7 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 16, "has_configs") self.addRoleName(Qt.UserRole + 16, "has_configs")
self.addRoleName(Qt.UserRole + 17, "supported_configs") self.addRoleName(Qt.UserRole + 17, "supported_configs")
self.addRoleName(Qt.UserRole + 18, "download_count") self.addRoleName(Qt.UserRole + 18, "download_count")
self.addRoleName(Qt.UserRole + 19, "tags")
# 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]
@ -78,13 +79,15 @@ class PackagesModel(ListModel):
"is_installed": package["is_installed"] if "is_installed" 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,
"download_count": package["download_count"] if "download_count" in package else 0 "download_count": package["download_count"] if "download_count" in package else 0,
"tags": package["tags"] if "tags" in package else []
}) })
# Filter on all the key-word arguments. # Filter on all the key-word arguments.
for key, value in self._filter.items(): for key, value in self._filter.items():
if "*" in value: if key is "tags":
key_filter = lambda item, value = value: value in item["tags"]
elif "*" in value:
key_filter = lambda candidate, key = key, value = value: self._matchRegExp(candidate, key, value) key_filter = lambda candidate, key = key, value = value: self._matchRegExp(candidate, key, value)
else: else:
key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value) key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value)

View File

@ -6,7 +6,7 @@ import json
import os import os
import tempfile import tempfile
import platform import platform
from typing import List from typing import cast, List
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
@ -71,7 +71,8 @@ class Toolbox(QObject, Extension):
"plugins_installed": [], "plugins_installed": [],
"materials_showcase": [], "materials_showcase": [],
"materials_available": [], "materials_available": [],
"materials_installed": [] "materials_installed": [],
"materials_generic": []
} # type: Dict[str, List[Any]] } # type: Dict[str, List[Any]]
# Models: # Models:
@ -83,7 +84,8 @@ class Toolbox(QObject, Extension):
"plugins_installed": PackagesModel(self), "plugins_installed": PackagesModel(self),
"materials_showcase": AuthorsModel(self), "materials_showcase": AuthorsModel(self),
"materials_available": PackagesModel(self), "materials_available": PackagesModel(self),
"materials_installed": PackagesModel(self) "materials_installed": PackagesModel(self),
"materials_generic": PackagesModel(self)
} # type: Dict[str, ListModel] } # type: Dict[str, ListModel]
# These properties are for keeping track of the UI state: # These properties are for keeping track of the UI state:
@ -102,6 +104,9 @@ class Toolbox(QObject, Extension):
self._active_package = None # type: Optional[Dict[str, Any]] self._active_package = None # type: Optional[Dict[str, Any]]
self._dialog = None #type: Optional[QObject] self._dialog = None #type: Optional[QObject]
self._confirm_reset_dialog = None #type: Optional[QObject]
self._resetUninstallVariables()
self._restart_required = False #type: bool self._restart_required = False #type: bool
# variables for the license agreement dialog # variables for the license agreement dialog
@ -130,6 +135,13 @@ class Toolbox(QObject, Extension):
filterChanged = pyqtSignal() filterChanged = pyqtSignal()
metadataChanged = pyqtSignal() metadataChanged = pyqtSignal()
showLicenseDialog = pyqtSignal() showLicenseDialog = pyqtSignal()
uninstallVariablesChanged = pyqtSignal()
def _resetUninstallVariables(self):
self._package_id_to_uninstall = None
self._package_name_to_uninstall = ""
self._package_used_materials = []
self._package_used_qualities = []
@pyqtSlot(result = str) @pyqtSlot(result = str)
def getLicenseDialogPluginName(self) -> str: def getLicenseDialogPluginName(self) -> str:
@ -168,7 +180,8 @@ class Toolbox(QObject, Extension):
"plugins_showcase": QUrl("{base_url}/showcase".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)), "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_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)) "materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url=self._api_url)),
"materials_generic": QUrl("{base_url}/packages?package_type=material&tags=generic".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.
@ -218,12 +231,19 @@ class Toolbox(QObject, Extension):
self._makeRequestByType("authors") self._makeRequestByType("authors")
self._makeRequestByType("plugins_showcase") self._makeRequestByType("plugins_showcase")
self._makeRequestByType("materials_showcase") self._makeRequestByType("materials_showcase")
self._makeRequestByType("materials_available")
self._makeRequestByType("materials_generic")
# Gather installed packages: # Gather installed packages:
self._updateInstalledModels() self._updateInstalledModels()
if not self._dialog: if not self._dialog:
self._dialog = self._createDialog("Toolbox.qml") self._dialog = self._createDialog("Toolbox.qml")
if not self._dialog:
Logger.log("e", "Unexpected error trying to create the 'Toolbox' dialog.")
return
self._dialog.show() self._dialog.show()
# Apply enabled/disabled state to installed plugins # Apply enabled/disabled state to installed plugins
@ -231,11 +251,16 @@ class Toolbox(QObject, Extension):
def _createDialog(self, qml_name: str) -> Optional[QObject]: def _createDialog(self, qml_name: str) -> Optional[QObject]:
Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name) Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name) plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if not plugin_path:
return None
path = os.path.join(plugin_path, "resources", "qml", qml_name)
dialog = self._application.createQmlComponent(path, {"toolbox": self}) dialog = self._application.createQmlComponent(path, {"toolbox": self})
if not dialog:
raise Exception("Failed to create toolbox dialog")
return dialog return dialog
def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]: def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]:
formatted = { formatted = {
"package_id": plugin["id"], "package_id": plugin["id"],
@ -294,9 +319,90 @@ class Toolbox(QObject, Extension):
self._restart_required = True self._restart_required = True
self.restartRequiredChanged.emit() self.restartRequiredChanged.emit()
## Check package usage and uninstall
# If the package is in use, you'll get a confirmation dialog to set everything to default
@pyqtSlot(str) @pyqtSlot(str)
def uninstall(self, plugin_id: str) -> None: def checkPackageUsageAndUninstall(self, package_id: str) -> None:
self._package_manager.removePackage(plugin_id, force_add = True) package_used_materials, package_used_qualities = self._package_manager.getMachinesUsingPackage(package_id)
if package_used_materials or package_used_qualities:
# Set up "uninstall variables" for resetMaterialsQualitiesAndUninstall
self._package_id_to_uninstall = package_id
package_info = self._package_manager.getInstalledPackageInfo(package_id)
self._package_name_to_uninstall = package_info.get("display_name", package_info.get("package_id"))
self._package_used_materials = package_used_materials
self._package_used_qualities = package_used_qualities
# Ask change to default material / profile
if self._confirm_reset_dialog is None:
self._confirm_reset_dialog = self._createDialog("ToolboxConfirmUninstallResetDialog.qml")
self.uninstallVariablesChanged.emit()
if self._confirm_reset_dialog is None:
Logger.log("e", "ToolboxConfirmUninstallResetDialog should have been initialized, but it is not. Not showing dialog and not uninstalling package.")
else:
self._confirm_reset_dialog.show()
else:
# Plain uninstall
self.uninstall(package_id)
@pyqtProperty(str, notify = uninstallVariablesChanged)
def pluginToUninstall(self):
return self._package_name_to_uninstall
@pyqtProperty(str, notify = uninstallVariablesChanged)
def uninstallUsedMaterials(self):
return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials])
@pyqtProperty(str, notify = uninstallVariablesChanged)
def uninstallUsedQualities(self):
return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities])
@pyqtSlot()
def closeConfirmResetDialog(self):
if self._confirm_reset_dialog is not None:
self._confirm_reset_dialog.close()
## Uses "uninstall variables" to reset qualities and materials, then uninstall
# It's used as an action on Confirm reset on Uninstall
@pyqtSlot()
def resetMaterialsQualitiesAndUninstall(self):
application = CuraApplication.getInstance()
material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager()
machine_manager = application.getMachineManager()
for global_stack, extruder_nr, container_id in self._package_used_materials:
default_material_node = material_manager.getDefaultMaterial(global_stack, extruder_nr, global_stack.extruders[extruder_nr].variant.getName())
machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack)
for global_stack, extruder_nr, container_id in self._package_used_qualities:
default_quality_group = quality_manager.getDefaultQualityType(global_stack)
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
self.uninstall(self._package_id_to_uninstall)
self._resetUninstallVariables()
self.closeConfirmResetDialog()
def _markPackageMaterialsAsToBeUninstalled(self, package_id: str) -> None:
container_registry = self._application.getContainerRegistry()
all_containers = self._package_manager.getPackageContainerIds(package_id)
for container_id in all_containers:
containers = container_registry.findInstanceContainers(id = container_id)
if not containers:
continue
container = containers[0]
if container.getMetaDataEntry("type") != "material":
continue
root_material_id = container.getMetaDataEntry("base_file")
root_material_containers = container_registry.findInstanceContainers(id = root_material_id)
if not root_material_containers:
continue
root_material_container = root_material_containers[0]
root_material_container.setMetaDataEntry("removed", True)
@pyqtSlot(str)
def uninstall(self, package_id: str) -> None:
self._package_manager.removePackage(package_id, force_add = True)
self.installChanged.emit() self.installChanged.emit()
self._updateInstalledModels() self._updateInstalledModels()
self.metadataChanged.emit() self.metadataChanged.emit()
@ -400,6 +506,22 @@ class Toolbox(QObject, Extension):
def isInstalled(self, package_id: str) -> bool: def isInstalled(self, package_id: str) -> bool:
return self._package_manager.isPackageInstalled(package_id) return self._package_manager.isPackageInstalled(package_id)
@pyqtSlot(str, result = int)
def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
count = 0
for package in self._metadata["materials_installed"]:
if package["author"]["author_id"] == author_id:
count += 1
return count
@pyqtSlot(str, result = int)
def getTotalNumberOfPackagesByAuthor(self, author_id: str) -> int:
count = 0
for package in self._metadata["materials_available"]:
if package["author"]["author_id"] == author_id:
count += 1
return count
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def isEnabled(self, package_id: str) -> bool: def isEnabled(self, package_id: str) -> bool:
if package_id in self._plugin_registry.getActivePlugins(): if package_id in self._plugin_registry.getActivePlugins():
@ -522,6 +644,8 @@ class Toolbox(QObject, Extension):
self._models[type].setFilter({"type": "plugin"}) self._models[type].setFilter({"type": "plugin"})
if type is "authors": if type is "authors":
self._models[type].setFilter({"package_types": "material"}) self._models[type].setFilter({"package_types": "material"})
if type is "materials_generic":
self._models[type].setFilter({"tags": "generic"})
self.metadataChanged.emit() self.metadataChanged.emit()
@ -621,27 +745,31 @@ class Toolbox(QObject, Extension):
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, notify = metadataChanged)
def authorsModel(self) -> AuthorsModel: def authorsModel(self) -> AuthorsModel:
return self._models["authors"] return cast(AuthorsModel, self._models["authors"])
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, notify = metadataChanged)
def packagesModel(self) -> PackagesModel: def packagesModel(self) -> PackagesModel:
return self._models["packages"] return cast(PackagesModel, self._models["packages"])
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, notify = metadataChanged)
def pluginsShowcaseModel(self) -> PackagesModel: def pluginsShowcaseModel(self) -> PackagesModel:
return self._models["plugins_showcase"] return cast(PackagesModel, self._models["plugins_showcase"])
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, notify = metadataChanged)
def pluginsInstalledModel(self) -> PackagesModel: def pluginsInstalledModel(self) -> PackagesModel:
return self._models["plugins_installed"] return cast(PackagesModel, self._models["plugins_installed"])
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, notify = metadataChanged)
def materialsShowcaseModel(self) -> PackagesModel: def materialsShowcaseModel(self) -> AuthorsModel:
return self._models["materials_showcase"] return cast(AuthorsModel, self._models["materials_showcase"])
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, notify = metadataChanged)
def materialsInstalledModel(self) -> PackagesModel: def materialsInstalledModel(self) -> PackagesModel:
return self._models["materials_installed"] return cast(PackagesModel, self._models["materials_installed"])
@pyqtProperty(QObject, notify=metadataChanged)
def materialsGenericModel(self) -> PackagesModel:
return cast(PackagesModel, self._models["materials_generic"])

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 typing import Any, cast, Set, Tuple, Union from typing import Any, cast, Optional, Set, Tuple, Union
from UM.FileHandler.FileHandler import FileHandler from UM.FileHandler.FileHandler import FileHandler
from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary). from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
@ -9,6 +9,7 @@ from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer as
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Mesh.MeshWriter import MeshWriter # For typing
from UM.Message import Message from UM.Message import Message
from UM.Qt.Duration import Duration, DurationFormat from UM.Qt.Duration import Duration, DurationFormat
from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing. from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
@ -103,8 +104,13 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
else: else:
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite() file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
#Create a list from the supported file formats string. #Create a list from the supported file formats string.
machine_file_formats = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";") if not global_stack:
Logger.log("e", "Missing global stack!")
return
machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
machine_file_formats = [file_type.strip() for file_type in machine_file_formats] machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format. #Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"): if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"):
@ -125,7 +131,14 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
else: else:
writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"])) writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"]))
if not writer:
Logger.log("e", "Unexpected error when trying to get the FileWriter")
return
#This function pauses with the yield, waiting on instructions on which printer it needs to print with. #This function pauses with the yield, waiting on instructions on which printer it needs to print with.
if not writer:
Logger.log("e", "Missing file or mesh writer!")
return
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes) self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
self._sending_job.send(None) #Start the generator. self._sending_job.send(None) #Start the generator.
@ -205,14 +218,14 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
yield #To prevent having to catch the StopIteration exception. yield #To prevent having to catch the StopIteration exception.
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None: def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
self._write_job_progress_message.hide() if self._write_job_progress_message:
self._write_job_progress_message.hide()
self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
title = i18n_catalog.i18nc("@info:title", "Sending Data")) title = i18n_catalog.i18nc("@info:title", "Sending Data"))
self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None, description = "") self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None, description = "")
self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered) self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
self._progress_message.show() self._progress_message.show()
parts = [] parts = []
target_printer, preferred_format, stream = self._dummy_lambdas target_printer, preferred_format, stream = self._dummy_lambdas
@ -249,7 +262,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self.activePrinterChanged.emit() self.activePrinterChanged.emit()
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None: def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
self._progress_message.hide() if self._progress_message:
self._progress_message.hide()
self._compressing_gcode = False self._compressing_gcode = False
self._sending_gcode = False self._sending_gcode = False

View File

@ -19,5 +19,5 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
def setJobState(self, job: "PrintJobOutputModel", state: str): def setJobState(self, job: "PrintJobOutputModel", state: str):
data = "{\"action\": \"%s\"}" % state data = "{\"action\": \"%s\"}" % state
self._output_device.put("print_jobs/%s/action" % job.key, data, onFinished=None) self._output_device.put("print_jobs/%s/action" % job.key, data, on_finished=None)

View File

@ -3,7 +3,7 @@
import os.path import os.path
import time import time
from typing import Optional from typing import cast, Optional
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject
@ -108,8 +108,9 @@ class DiscoverUM3Action(MachineAction):
# Find all the places where there is the same group name and change it accordingly # Find all the places where there is the same group name and change it accordingly
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name) CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
else: else:
global_container_stack.addMetaDataEntry("connect_group_name", group_name) global_container_stack.setMetaDataEntry("connect_group_name", group_name)
global_container_stack.addMetaDataEntry("hidden", False) # Set the default value for "hidden", which is used when you have a group with multiple types of printers
global_container_stack.setMetaDataEntry("hidden", False)
if self._network_plugin: if self._network_plugin:
# Ensure that the connection states are refreshed. # Ensure that the connection states are refreshed.
@ -130,7 +131,7 @@ class DiscoverUM3Action(MachineAction):
global_container_stack.removeMetaDataEntry("network_authentication_key") global_container_stack.removeMetaDataEntry("network_authentication_key")
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key) CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
else: else:
global_container_stack.addMetaDataEntry("um_network_key", key) global_container_stack.setMetaDataEntry("um_network_key", key)
if self._network_plugin: if self._network_plugin:
# Ensure that the connection states are refreshed. # Ensure that the connection states are refreshed.
@ -170,7 +171,10 @@ class DiscoverUM3Action(MachineAction):
Logger.log("d", "Creating additional ui components for UM3.") Logger.log("d", "Creating additional ui components for UM3.")
# Create networking dialog # Create networking dialog
path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml") plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting")
if not plugin_path:
return
path = os.path.join(plugin_path, "UM3InfoComponents.qml")
self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
if not self.__additional_components_view: if not self.__additional_components_view:
Logger.log("w", "Could not create ui components for UM3.") Logger.log("w", "Could not create ui components for UM3.")

View File

@ -165,7 +165,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
file_name = "none.xml" file_name = "none.xml"
self.postForm("materials", "form-data; name=\"file\";filename=\"%s\"" % file_name, xml_data.encode(), onFinished=None) self.postForm("materials", "form-data; name=\"file\";filename=\"%s\"" % file_name, xml_data.encode(), on_finished=None)
except NotImplementedError: except NotImplementedError:
# If the material container is not the most "generic" one it can't be serialized an will raise a # If the material container is not the most "generic" one it can't be serialized an will raise a
@ -270,7 +270,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
file_name = "%s.gcode.gz" % CuraApplication.getInstance().getPrintInformation().jobName file_name = "%s.gcode.gz" % CuraApplication.getInstance().getPrintInformation().jobName
self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode, self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
onFinished=self._onPostPrintJobFinished) on_finished=self._onPostPrintJobFinished)
return return
@ -381,8 +381,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self._checkAuthentication() self._checkAuthentication()
# We don't need authentication for requesting info, so we can go right ahead with requesting this. # We don't need authentication for requesting info, so we can go right ahead with requesting this.
self.get("printer", onFinished=self._onGetPrinterDataFinished) self.get("printer", on_finished=self._onGetPrinterDataFinished)
self.get("print_job", onFinished=self._onGetPrintJobFinished) self.get("print_job", on_finished=self._onGetPrintJobFinished)
def _resetAuthenticationRequestedMessage(self): def _resetAuthenticationRequestedMessage(self):
if self._authentication_requested_message: if self._authentication_requested_message:
@ -404,7 +404,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def _verifyAuthentication(self): def _verifyAuthentication(self):
Logger.log("d", "Attempting to verify authentication") Logger.log("d", "Attempting to verify authentication")
# This will ensure that the "_onAuthenticationRequired" is triggered, which will setup the authenticator. # This will ensure that the "_onAuthenticationRequired" is triggered, which will setup the authenticator.
self.get("auth/verify", onFinished=self._onVerifyAuthenticationCompleted) self.get("auth/verify", on_finished=self._onVerifyAuthenticationCompleted)
def _onVerifyAuthenticationCompleted(self, reply): def _onVerifyAuthenticationCompleted(self, reply):
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
@ -426,7 +426,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def _checkAuthentication(self): def _checkAuthentication(self):
Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey()) Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
self.get("auth/check/" + str(self._authentication_id), onFinished=self._onCheckAuthenticationFinished) self.get("auth/check/" + str(self._authentication_id), on_finished=self._onCheckAuthenticationFinished)
def _onCheckAuthenticationFinished(self, reply): def _onCheckAuthenticationFinished(self, reply):
if str(self._authentication_id) not in reply.url().toString(): if str(self._authentication_id) not in reply.url().toString():
@ -455,18 +455,18 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self.setAuthenticationState(AuthState.AuthenticationDenied) self.setAuthenticationState(AuthState.AuthenticationDenied)
self._authentication_failed_message.show() self._authentication_failed_message.show()
def _saveAuthentication(self): def _saveAuthentication(self) -> None:
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if self._authentication_key is None:
Logger.log("e", "Authentication key is None, nothing to save.")
return
if self._authentication_id is None:
Logger.log("e", "Authentication id is None, nothing to save.")
return
if global_container_stack: if global_container_stack:
if "network_authentication_key" in global_container_stack.getMetaData(): global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
else:
global_container_stack.addMetaDataEntry("network_authentication_key", self._authentication_key)
if "network_authentication_id" in global_container_stack.getMetaData(): global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id)
global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id)
else:
global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
# Force save so we are sure the data is not lost. # Force save so we are sure the data is not lost.
CuraApplication.getInstance().saveStack(global_container_stack) CuraApplication.getInstance().saveStack(global_container_stack)
@ -502,7 +502,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self.post("auth/request", self.post("auth/request",
json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(), json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
"user": self._getUserName()}).encode(), "user": self._getUserName()}).encode(),
onFinished=self._onRequestAuthenticationFinished) on_finished=self._onRequestAuthenticationFinished)
self.setAuthenticationState(AuthState.AuthenticationRequested) self.setAuthenticationState(AuthState.AuthenticationRequested)
@ -637,4 +637,4 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
result = "********" + result result = "********" + result
return result return result
return self._authentication_key return self._authentication_key

View File

@ -31,11 +31,11 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
def setJobState(self, job: "PrintJobOutputModel", state: str): def setJobState(self, job: "PrintJobOutputModel", state: str):
data = "{\"target\": \"%s\"}" % state data = "{\"target\": \"%s\"}" % state
self._output_device.put("print_job/state", data, onFinished=None) self._output_device.put("print_job/state", data, on_finished=None)
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int): def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
data = str(temperature) data = str(temperature)
self._output_device.put("printer/bed/temperature/target", data, onFinished=self._onPutBedTemperatureCompleted) self._output_device.put("printer/bed/temperature/target", data, on_finished=self._onPutBedTemperatureCompleted)
def _onPutBedTemperatureCompleted(self, reply): def _onPutBedTemperatureCompleted(self, reply):
if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"): if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
@ -51,10 +51,10 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
new_y = head_pos.y + y new_y = head_pos.y + y
new_z = head_pos.z + z new_z = head_pos.z + z
data = "{\n\"x\":%s,\n\"y\":%s,\n\"z\":%s\n}" %(new_x, new_y, new_z) data = "{\n\"x\":%s,\n\"y\":%s,\n\"z\":%s\n}" %(new_x, new_y, new_z)
self._output_device.put("printer/heads/0/position", data, onFinished=None) self._output_device.put("printer/heads/0/position", data, on_finished=None)
def homeBed(self, printer): def homeBed(self, printer):
self._output_device.put("printer/heads/0/position/z", "0", onFinished=None) self._output_device.put("printer/heads/0/position/z", "0", on_finished=None)
def _onPreheatBedTimerFinished(self): def _onPreheatBedTimerFinished(self):
self.setTargetBedTemperature(self._preheat_printer, 0) self.setTargetBedTemperature(self._preheat_printer, 0)
@ -89,7 +89,7 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
printer.updateIsPreheating(True) printer.updateIsPreheating(True)
return return
self._output_device.put("printer/bed/pre_heat", data, onFinished = self._onPutPreheatBedCompleted) self._output_device.put("printer/bed/pre_heat", data, on_finished = self._onPutPreheatBedCompleted)
printer.updateIsPreheating(True) printer.updateIsPreheating(True)
self._preheat_request_in_progress = True self._preheat_request_in_progress = True

View File

@ -26,6 +26,10 @@ UM.Dialog
{ {
resetPrintersModel() resetPrintersModel()
} }
else
{
OutputDevice.cancelPrintSelection()
}
} }
title: catalog.i18nc("@title:window", "Print over network") title: catalog.i18nc("@title:window", "Print over network")

View File

@ -23,7 +23,7 @@ if TYPE_CHECKING:
# #
# This way it won't freeze up the interface while sending those materials. # This way it won't freeze up the interface while sending those materials.
class SendMaterialJob(Job): class SendMaterialJob(Job):
def __init__(self, device: "ClusterUM3OutputDevice"): def __init__(self, device: "ClusterUM3OutputDevice") -> None:
super().__init__() super().__init__()
self.device = device #type: ClusterUM3OutputDevice self.device = device #type: ClusterUM3OutputDevice

View File

@ -72,7 +72,7 @@ class AutoDetectBaudJob(Job):
while timeout_time > time(): while timeout_time > time():
line = serial.readline() line = serial.readline()
if b"ok T:" in line: if b"ok " in line and b"T:" in line:
successful_responses += 1 successful_responses += 1
if successful_responses >= 3: if successful_responses >= 3:
self.setResult(baud_rate) self.setResult(baud_rate)

View File

@ -88,6 +88,25 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_received = Event() self._command_received = Event()
self._command_received.set() self._command_received.set()
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
# This is a callback function that checks if there is any printing in progress via USB when the application tries
# to exit. If so, it will show a confirmation before
def _checkActivePrintingUponAppExit(self) -> None:
application = CuraApplication.getInstance()
if not self._is_printing:
# This USB printer is not printing, so we have nothing to do. Call the next callback if exists.
application.triggerNextExitCheck()
return
application.setConfirmExitDialogCallback(self._onConfirmExitDialogResult)
application.showConfirmExitDialog.emit(catalog.i18nc("@label", "A USB print is in progress, closing Cura will stop this print. Are you sure?"))
def _onConfirmExitDialogResult(self, result: bool) -> None:
if result:
application = CuraApplication.getInstance()
application.triggerNextExitCheck()
## Reset USB device settings ## Reset USB device settings
# #
def resetDeviceSettings(self): def resetDeviceSettings(self):
@ -304,7 +323,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._firmware_name is None: if self._firmware_name is None:
self.sendCommand("M115") self.sendCommand("M115")
if b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed if (b"ok " in line and b"T:" in line) or b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line) extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
# Update all temperature values # Update all temperature values
matched_extruder_nrs = [] matched_extruder_nrs = []
@ -423,7 +442,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
elapsed_time = int(time() - self._print_start_time) elapsed_time = int(time() - self._print_start_time)
print_job = self._printers[0].activePrintJob print_job = self._printers[0].activePrintJob
if print_job is None: if print_job is None:
print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= Application.getInstance().getPrintInformation().jobName) print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= CuraApplication.getInstance().getPrintInformation().jobName)
print_job.updateState("printing") print_job.updateState("printing")
self._printers[0].updateActivePrintJob(print_job) self._printers[0].updateActivePrintJob(print_job)

View File

@ -47,10 +47,7 @@ class UM2UpgradeSelection(MachineAction):
variant_container = global_container_stack.extruders["0"].variant variant_container = global_container_stack.extruders["0"].variant
if has_variants: if has_variants:
if "has_variants" in global_container_stack.getMetaData(): global_container_stack.setMetaDataEntry("has_variants", True)
global_container_stack.setMetaDataEntry("has_variants", True)
else:
global_container_stack.addMetaDataEntry("has_variants", True)
# Set the variant container to a sane default # Set the variant container to a sane default
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()

View File

@ -6,6 +6,55 @@ import io
from UM.VersionUpgrade import VersionUpgrade from UM.VersionUpgrade import VersionUpgrade
deleted_settings = {"prime_tower_wall_thickness", "dual_pre_wipe", "prime_tower_purge_volume"}
_RENAMED_MATERIAL_PROFILES = {
"dsm_arnitel2045_175_cartesio_0.25_mm": "dsm_arnitel2045_175_cartesio_0.25mm_thermoplastic_extruder",
"dsm_arnitel2045_175_cartesio_0.4_mm": "dsm_arnitel2045_175_cartesio_0.4mm_thermoplastic_extruder",
"dsm_arnitel2045_175_cartesio_0.8_mm": "dsm_arnitel2045_175_cartesio_0.8mm_thermoplastic_extruder",
"dsm_novamid1070_175_cartesio_0.25_mm": "dsm_novamid1070_175_cartesio_0.25mm_thermoplastic_extruder",
"dsm_novamid1070_175_cartesio_0.4_mm": "dsm_novamid1070_175_cartesio_0.4mm_thermoplastic_extruder",
"dsm_novamid1070_175_cartesio_0.8_mm": "dsm_novamid1070_175_cartesio_0.8mm_thermoplastic_extruder",
"generic_abs_175_cartesio_0.25_mm": "generic_abs_175_cartesio_0.25mm_thermoplastic_extruder",
"generic_abs_175_cartesio_0.4_mm": "generic_abs_175_cartesio_0.4mm_thermoplastic_extruder",
"generic_abs_175_cartesio_0.8_mm": "generic_abs_175_cartesio_0.8mm_thermoplastic_extruder",
"generic_hips_175_cartesio_0.25_mm": "generic_hips_175_cartesio_0.25mm_thermoplastic_extruder",
"generic_hips_175_cartesio_0.4_mm": "generic_hips_175_cartesio_0.4mm_thermoplastic_extruder",
"generic_hips_175_cartesio_0.8_mm": "generic_hips_175_cartesio_0.8mm_thermoplastic_extruder",
"generic_nylon_175_cartesio_0.25_mm": "generic_nylon_175_cartesio_0.25mm_thermoplastic_extruder",
"generic_nylon_175_cartesio_0.4_mm": "generic_nylon_175_cartesio_0.4mm_thermoplastic_extruder",
"generic_nylon_175_cartesio_0.8_mm": "generic_nylon_175_cartesio_0.8mm_thermoplastic_extruder",
"generic_pc_cartesio_0.25_mm": "generic_pc_cartesio_0.25mm_thermoplastic_extruder",
"generic_pc_cartesio_0.4_mm": "generic_pc_cartesio_0.4mm_thermoplastic_extruder",
"generic_pc_cartesio_0.8_mm": "generic_pc_cartesio_0.8mm_thermoplastic_extruder",
"generic_pc_175_cartesio_0.25_mm": "generic_pc_175_cartesio_0.25mm_thermoplastic_extruder",
"generic_pc_175_cartesio_0.4_mm": "generic_pc_175_cartesio_0.4mm_thermoplastic_extruder",
"generic_pc_175_cartesio_0.8_mm": "generic_pc_175_cartesio_0.8mm_thermoplastic_extruder",
"generic_petg_175_cartesio_0.25_mm": "generic_petg_175_cartesio_0.25mm_thermoplastic_extruder",
"generic_petg_175_cartesio_0.4_mm": "generic_petg_175_cartesio_0.4mm_thermoplastic_extruder",
"generic_petg_175_cartesio_0.8_mm": "generic_petg_175_cartesio_0.8mm_thermoplastic_extruder",
"generic_pla_175_cartesio_0.25_mm": "generic_pla_175_cartesio_0.25mm_thermoplastic_extruder",
"generic_pla_175_cartesio_0.4_mm": "generic_pla_175_cartesio_0.4mm_thermoplastic_extruder",
"generic_pla_175_cartesio_0.8_mm": "generic_pla_175_cartesio_0.8mm_thermoplastic_extruder",
"generic_pva_cartesio_0.25_mm": "generic_pva_cartesio_0.25mm_thermoplastic_extruder",
"generic_pva_cartesio_0.4_mm": "generic_pva_cartesio_0.4mm_thermoplastic_extruder",
"generic_pva_cartesio_0.8_mm": "generic_pva_cartesio_0.8mm_thermoplastic_extruder",
"generic_pva_175_cartesio_0.25_mm": "generic_pva_175_cartesio_0.25mm_thermoplastic_extruder",
"generic_pva_175_cartesio_0.4_mm": "generic_pva_175_cartesio_0.4mm_thermoplastic_extruder",
"generic_pva_175_cartesio_0.8_mm": "generic_pva_175_cartesio_0.8mm_thermoplastic_extruder",
"ultimaker_pc_black_cartesio_0.25_mm": "ultimaker_pc_black_cartesio_0.25mm_thermoplastic_extruder",
"ultimaker_pc_black_cartesio_0.4_mm": "ultimaker_pc_black_cartesio_0.4mm_thermoplastic_extruder",
"ultimaker_pc_black_cartesio_0.8_mm": "ultimaker_pc_black_cartesio_0.8mm_thermoplastic_extruder",
"ultimaker_pc_transparent_cartesio_0.25_mm": "ultimaker_pc_transparent_cartesio_0.25mm_thermoplastic_extruder",
"ultimaker_pc_transparent_cartesio_0.4_mm": "ultimaker_pc_transparent_cartesio_0.4mm_thermoplastic_extruder",
"ultimaker_pc_transparent_cartesio_0.8_mm": "ultimaker_pc_transparent_cartesio_0.8mm_thermoplastic_extruder",
"ultimaker_pc_white_cartesio_0.25_mm": "ultimaker_pc_white_cartesio_0.25mm_thermoplastic_extruder",
"ultimaker_pc_white_cartesio_0.4_mm": "ultimaker_pc_white_cartesio_0.4mm_thermoplastic_extruder",
"ultimaker_pc_white_cartesio_0.8_mm": "ultimaker_pc_white_cartesio_0.8mm_thermoplastic_extruder",
"ultimaker_pva_cartesio_0.25_mm": "ultimaker_pva_cartesio_0.25mm_thermoplastic_extruder",
"ultimaker_pva_cartesio_0.4_mm": "ultimaker_pva_cartesio_0.4mm_thermoplastic_extruder",
"ultimaker_pva_cartesio_0.8_mm": "ultimaker_pva_cartesio_0.8mm_thermoplastic_extruder"
}
## Upgrades configurations from the state they were in at version 3.4 to the ## Upgrades configurations from the state they were in at version 3.4 to the
# state they should be in at version 4.0. # state they should be in at version 4.0.
@ -53,6 +102,10 @@ class VersionUpgrade34to40(VersionUpgrade):
parser["general"]["version"] = "4" parser["general"]["version"] = "4"
parser["metadata"]["setting_version"] = "5" parser["metadata"]["setting_version"] = "5"
#Update the name of the quality profile.
if parser["containers"]["3"] in _RENAMED_MATERIAL_PROFILES:
parser["containers"]["3"] = _RENAMED_MATERIAL_PROFILES[parser["containers"]["3"]]
result = io.StringIO() result = io.StringIO()
parser.write(result) parser.write(result)
return [filename], [result.getvalue()] return [filename], [result.getvalue()]
@ -68,6 +121,11 @@ class VersionUpgrade34to40(VersionUpgrade):
parser["metadata"]["setting_version"] = "5" parser["metadata"]["setting_version"] = "5"
self._resetConcentric3DInfillPattern(parser) self._resetConcentric3DInfillPattern(parser)
if "values" in parser:
for deleted_setting in deleted_settings:
if deleted_setting not in parser["values"]:
continue
del parser["values"][deleted_setting]
result = io.StringIO() result = io.StringIO()
parser.write(result) parser.write(result)

View File

@ -0,0 +1,35 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse the resulting config files.
import pytest #To register tests with.
import VersionUpgrade34to40 #The module we're testing.
## Creates an instance of the upgrader to test with.
@pytest.fixture
def upgrader():
return VersionUpgrade34to40.VersionUpgrade34to40()
test_upgrade_version_nr_data = [
("Empty config file",
"""[general]
version = 5
[metadata]
setting_version = 4
"""
)
]
## Tests whether the version numbers are updated.
@pytest.mark.parametrize("test_name, file_data", test_upgrade_version_nr_data)
def test_upgradeVersionNr(test_name, file_data, upgrader):
#Perform the upgrade.
_, upgraded_instances = upgrader.upgradePreferences(file_data, "<string>")
upgraded_instance = upgraded_instances[0]
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(upgraded_instance)
#Check the new version.
assert parser["general"]["version"] == "6"
assert parser["metadata"]["setting_version"] == "5"

View File

@ -2,6 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from math import pi, sin, cos, sqrt from math import pi, sin, cos, sqrt
from typing import Dict
import numpy import numpy
@ -42,7 +43,7 @@ class X3DReader(MeshReader):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._supported_extensions = [".x3d"] self._supported_extensions = [".x3d"]
self._namespaces = {} self._namespaces = {} # type: Dict[str, str]
# 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

View File

@ -662,6 +662,23 @@
} }
} }
}, },
"VersionUpgrade34to40": {
"package_info": {
"package_id": "VersionUpgrade34to40",
"package_type": "plugin",
"display_name": "Version Upgrade 3.4 to 4.0",
"description": "Upgrades configurations from Cura 3.4 to Cura 4.0.",
"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"
}
}
},
"X3DReader": { "X3DReader": {
"package_info": { "package_info": {
"package_id": "X3DReader", "package_id": "X3DReader",
@ -713,6 +730,240 @@
} }
} }
}, },
"GenericABS": {
"package_info": {
"package_id": "GenericABS",
"package_type": "material",
"display_name": "Generic ABS",
"description": "The generic ABS profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericBAM": {
"package_info": {
"package_id": "GenericBAM",
"package_type": "material",
"display_name": "Generic BAM",
"description": "The generic BAM profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericCPE": {
"package_info": {
"package_id": "GenericCPE",
"package_type": "material",
"display_name": "Generic CPE",
"description": "The generic CPE profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericCPEPlus": {
"package_info": {
"package_id": "GenericCPEPlus",
"package_type": "material",
"display_name": "Generic CPE+",
"description": "The generic CPE+ profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericHIPS": {
"package_info": {
"package_id": "GenericHIPS",
"package_type": "material",
"display_name": "Generic HIPS",
"description": "The generic HIPS profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericNylon": {
"package_info": {
"package_id": "GenericNylon",
"package_type": "material",
"display_name": "Generic Nylon",
"description": "The generic Nylon profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericPC": {
"package_info": {
"package_id": "GenericPC",
"package_type": "material",
"display_name": "Generic PC",
"description": "The generic PC profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericPETG": {
"package_info": {
"package_id": "GenericPETG",
"package_type": "material",
"display_name": "Generic PETG",
"description": "The generic PETG profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericPLA": {
"package_info": {
"package_id": "GenericPLA",
"package_type": "material",
"display_name": "Generic PLA",
"description": "The generic PLA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericPP": {
"package_info": {
"package_id": "GenericPP",
"package_type": "material",
"display_name": "Generic PP",
"description": "The generic PP profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericPVA": {
"package_info": {
"package_id": "GenericPVA",
"package_type": "material",
"display_name": "Generic PVA",
"description": "The generic PVA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericToughPLA": {
"package_info": {
"package_id": "GenericToughPLA",
"package_type": "material",
"display_name": "Generic Tough PLA",
"description": "The generic Tough PLA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericTPU": {
"package_info": {
"package_id": "GenericTPU",
"package_type": "material",
"display_name": "Generic TPU",
"description": "The generic TPU profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"DagomaChromatikPLA": { "DagomaChromatikPLA": {
"package_info": { "package_info": {
"package_id": "DagomaChromatikPLA", "package_id": "DagomaChromatikPLA",

View File

@ -24,7 +24,6 @@
"machine_depth": { "default_value": 149.86 }, "machine_depth": { "default_value": 149.86 },
"machine_height": { "default_value": 99.822 }, "machine_height": { "default_value": 99.822 },
"machine_center_is_zero": { "default_value": true }, "machine_center_is_zero": { "default_value": true },
"machine_nozzle_size": { "default_value": 0.4 },
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {
"default_value": [ "default_value": [
[ 0, 0 ], [ 0, 0 ],

View File

@ -18,7 +18,6 @@
"overrides": { "overrides": {
"machine_name": { "default_value": "3Dator" }, "machine_name": { "default_value": "3Dator" },
"machine_nozzle_size": { "default_value": 0.5 },
"speed_travel": { "default_value": 120 }, "speed_travel": { "default_value": 120 },
"prime_tower_size": { "default_value": 8.660254037844387 }, "prime_tower_size": { "default_value": 8.660254037844387 },
"infill_sparse_density": { "default_value": 20 }, "infill_sparse_density": { "default_value": 20 },

View File

@ -26,9 +26,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_polygon": { "machine_head_polygon": {
"default_value": [ "default_value": [
[75, 18], [75, 18],

View File

@ -45,10 +45,6 @@
{ {
"default_value": false "default_value": false
}, },
"machine_nozzle_size":
{
"default_value": 0.4
},
"gantry_height": "gantry_height":
{ {
"default_value": 0 "default_value": 0

View File

@ -21,7 +21,6 @@
"prime_tower_size": { "default_value": 7.745966692414834 }, "prime_tower_size": { "default_value": 7.745966692414834 },
"machine_name": { "default_value": "BFB_Test" }, "machine_name": { "default_value": "BFB_Test" },
"machine_heated_bed": { "default_value": false }, "machine_heated_bed": { "default_value": false },
"machine_nozzle_size": { "default_value": 0.5 },
"speed_layer_0": { "default_value": 25 }, "speed_layer_0": { "default_value": 25 },
"machine_width": { "default_value": 275 }, "machine_width": { "default_value": 275 },
"machine_gcode_flavor": { "default_value": "BFB" }, "machine_gcode_flavor": { "default_value": "BFB" },

View File

@ -54,7 +54,6 @@
"prime_tower_position_y": { "default_value": 178 }, "prime_tower_position_y": { "default_value": 178 },
"prime_tower_wipe_enabled": { "default_value": false }, "prime_tower_wipe_enabled": { "default_value": false },
"prime_tower_min_volume": { "default_value": 50 }, "prime_tower_min_volume": { "default_value": 50 },
"dual_pre_wipe": { "default_value": false },
"prime_blob_enable": { "enabled": true }, "prime_blob_enable": { "enabled": true },

View File

@ -54,7 +54,6 @@
"prime_tower_position_y": { "default_value": 178 }, "prime_tower_position_y": { "default_value": 178 },
"prime_tower_wipe_enabled": { "default_value": false }, "prime_tower_wipe_enabled": { "default_value": false },
"prime_tower_min_volume": { "default_value": 50 }, "prime_tower_min_volume": { "default_value": 50 },
"dual_pre_wipe": { "default_value": false },
"prime_blob_enable": { "enabled": true }, "prime_blob_enable": { "enabled": true },

View File

@ -53,7 +53,6 @@
"prime_tower_position_y": { "default_value": 178 }, "prime_tower_position_y": { "default_value": 178 },
"prime_tower_wipe_enabled": { "default_value": false }, "prime_tower_wipe_enabled": { "default_value": false },
"prime_tower_min_volume": { "default_value": 50 }, "prime_tower_min_volume": { "default_value": 50 },
"dual_pre_wipe": { "default_value": false },
"prime_blob_enable": { "enabled": true }, "prime_blob_enable": { "enabled": true },

View File

@ -15,7 +15,7 @@
"has_variants": true, "has_variants": true,
"variants_name": "Tool", "variants_name": "Tool",
"preferred_variant_name": "0.8 mm", "preferred_variant_name": "0.8mm thermoplastic extruder",
"preferred_material": "generic_pla", "preferred_material": "generic_pla",
"preferred_quality_type": "normal", "preferred_quality_type": "normal",
@ -44,7 +44,7 @@
"material_print_temp_wait": { "default_value": false }, "material_print_temp_wait": { "default_value": false },
"material_bed_temp_wait": { "default_value": false }, "material_bed_temp_wait": { "default_value": false },
"prime_tower_enable": { "default_value": false }, "prime_tower_enable": { "default_value": false },
"prime_tower_wall_thickness": { "resolve": 0.7 }, "prime_tower_min_volume": { "value": "0.7" },
"prime_tower_size": { "value": 24.0 }, "prime_tower_size": { "value": 24.0 },
"prime_tower_position_x": { "value": 125 }, "prime_tower_position_x": { "value": 125 },
"prime_tower_position_y": { "value": 70 }, "prime_tower_position_y": { "value": 70 },

View File

@ -31,9 +31,6 @@
[30, 34] [30, 34]
] ]
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"layer_height_0": { "layer_height_0": {
"default_value": 0.2 "default_value": 0.2
}, },

View File

@ -1,6 +1,6 @@
{ {
"version": 2, "version": 2,
"name": "Custom FDM printer", "name": "Custom FFF printer",
"inherits": "fdmprinter", "inherits": "fdmprinter",
"metadata": { "metadata": {
"visible": true, "visible": true,

View File

@ -9,6 +9,8 @@
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "discoeasy200.stl", "platform": "discoeasy200.stl",
"platform_offset": [ 105, -59, 280], "platform_offset": [ 105, -59, 280],
"has_machine_quality": true,
"has_materials": true,
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "dagoma_discoeasy200_extruder_0" "0": "dagoma_discoeasy200_extruder_0"
@ -27,37 +29,46 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {
"default_value": [ "default_value": [
[17, 70], [-17, -70],
[17, -40], [-17, 40],
[-17, -40], [17, 40],
[17, 70] [17, -70]
] ]
}, },
"gantry_height": { "gantry_height": {
"default_value": 10 "default_value": 10
}, },
"machine_start_gcode": { "machine_start_gcode": {
"default_value": ";Gcode by Cura\nG90\nM106 S250\nG28 X Y\nG1 X50\nM109 S180\nG28\nM104 S{material_print_temperature_layer_0}\nG29\nM107\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E10\nG92 E0\nG1 Z3\nG1 F6000\n" "default_value": ";Gcode by Cura\nG90\nM106 S255\nG28 X Y\nG1 X50\nM109 R90\nG28\nM104 S{material_print_temperature_layer_0}\nG29\nM107\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E10\nG92 E0\nG1 Z3\nG1 F6000\n"
}, },
"machine_end_gcode": { "machine_end_gcode": {
"default_value": "\nM104 S0\nM106 S255\nM140 S0\nG91\nG1 E-1 F300\nG1 Z+3 F3000\nG90\nG28 X Y\nM107\nM84\n" "default_value": "\nM104 S0\nM106 S255\nM140 S0\nG91\nG1 E-1 F300\nG1 Z+3 F3000\nG90\nG28 X Y\nM107\nM84\n"
}, },
"default_material_print_temperature": {
"default_value": 205
},
"speed_print": { "speed_print": {
"default_value": 60 "default_value": 60
}, },
"speed_travel": { "speed_travel": {
"value": "100" "default_value": 100
}, },
"retraction_amount": { "retraction_amount": {
"default_value": 3.5 "default_value": 3.5
}, },
"retraction_speed": { "retraction_speed": {
"default_value": 50 "default_value": 50
},
"adhesion_type": {
"default_value": "skirt"
},
"skirt_line_count": {
"default_value": 2
},
"layer_height_0": {
"default_value": 0.26
} }
} }
} }

View File

@ -1,5 +1,4 @@
{ {
"id": "Dagoma_neva",
"name": "Dagoma NEVA", "name": "Dagoma NEVA",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",
@ -10,6 +9,8 @@
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "neva.stl", "platform": "neva.stl",
"platform_offset": [ 0, 0, 0], "platform_offset": [ 0, 0, 0],
"has_machine_quality": true,
"has_materials": true,
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "dagoma_neva_extruder_0" "0": "dagoma_neva_extruder_0"
@ -28,15 +29,12 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": true "default_value": true
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {
"default_value": [ "default_value": [
[17, 40], [-36, -42],
[17, -70], [-36, 42],
[-17, -70], [36, 42],
[17, 40] [36, -42]
] ]
}, },
"gantry_height": { "gantry_height": {
@ -46,14 +44,17 @@
"default_value": "elliptic" "default_value": "elliptic"
}, },
"machine_gcode_flavor": { "machine_gcode_flavor": {
"default_value": "RepRap (RepRap)" "default_value": "RepRap"
}, },
"machine_start_gcode": { "machine_start_gcode": {
"default_value": ";Gcode by Cura\nG90\nG28\nM109 S100\nG29\nM104 S{material_print_temperature_layer_0}\nG0 X0 Y-85\nG0 Z0.26\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n" "default_value": ";Gcode by Cura\nG90\nG28\nM107\nM109 R100\nG29\nM109 S{material_print_temperature_layer_0} U-55 X55 V-85 Y-85 W0.26 Z0.26\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n"
}, },
"machine_end_gcode": { "machine_end_gcode": {
"default_value": "\nM104 S0\nM106 S255\nM140 S0\nG91\nG1 E-1 F300\nG1 Z+3 E-2 F9000\nG90\nG28\n" "default_value": "\nM104 S0\nM106 S255\nM140 S0\nG91\nG1 E-1 F300\nG1 Z+3 E-2 F9000\nG90\nG28\n"
}, },
"default_material_print_temperature": {
"default_value": 205
},
"speed_print": { "speed_print": {
"default_value": 40 "default_value": 40
}, },
@ -65,6 +66,15 @@
}, },
"retraction_speed": { "retraction_speed": {
"default_value": 60 "default_value": 60
},
"adhesion_type": {
"default_value": "skirt"
},
"skirt_line_count": {
"default_value": 2
},
"layer_height_0": {
"default_value": 0.26
} }
} }
} }

View File

@ -0,0 +1,80 @@
{
"name": "Dagoma NEVA Magis",
"version": 2,
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Dagoma",
"manufacturer": "Dagoma",
"file_formats": "text/x-gcode",
"platform": "neva.stl",
"platform_offset": [ 0, 0, 0],
"has_machine_quality": true,
"has_materials": true,
"machine_extruder_trains":
{
"0": "dagoma_neva_magis_extruder_0"
}
},
"overrides": {
"machine_width": {
"default_value": 195.55
},
"machine_height": {
"default_value": 205
},
"machine_depth": {
"default_value": 195.55
},
"machine_center_is_zero": {
"default_value": true
},
"machine_head_with_fans_polygon": {
"default_value": [
[-36, -42],
[-36, 42],
[36, 42],
[36, -42]
]
},
"gantry_height": {
"default_value": 0
},
"machine_shape": {
"default_value": "elliptic"
},
"machine_gcode_flavor": {
"default_value": "RepRap"
},
"machine_start_gcode": {
"default_value": ";Gcode by Cura\nG90\nG28\nM107\nM109 R100\nG29\nM109 S{material_print_temperature_layer_0} U-55 X55 V-85 Y-85 W0.26 Z0.26\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n"
},
"machine_end_gcode": {
"default_value": "\nM104 S0\nM106 S255\nM140 S0\nG91\nG1 E-1 F300\nG1 Z+3 E-2 F9000\nG90\nG28\n"
},
"default_material_print_temperature": {
"default_value": 205
},
"speed_print": {
"default_value": 40
},
"speed_travel": {
"default_value": 120
},
"retraction_amount": {
"default_value": 3.8
},
"retraction_speed": {
"default_value": 60
},
"adhesion_type": {
"default_value": "skirt"
},
"skirt_line_count": {
"default_value": 2
},
"layer_height_0": {
"default_value": 0.26
}
}
}

View File

@ -22,7 +22,6 @@
"speed_wall_0": { "default_value": 30 }, "speed_wall_0": { "default_value": 30 },
"speed_topbottom": { "default_value": 30 }, "speed_topbottom": { "default_value": 30 },
"layer_height": { "default_value": 0.2 }, "layer_height": { "default_value": 0.2 },
"machine_nozzle_size": { "default_value": 0.5 },
"speed_print": { "default_value": 30 }, "speed_print": { "default_value": 30 },
"speed_infill": { "default_value": 30 }, "speed_infill": { "default_value": 30 },
"machine_extruder_count": { "default_value": 1 }, "machine_extruder_count": { "default_value": 1 },

View File

@ -23,7 +23,6 @@
"machine_height": { "default_value": 250 }, "machine_height": { "default_value": 250 },
"machine_depth": { "default_value": 190 }, "machine_depth": { "default_value": 190 },
"machine_center_is_zero": { "default_value": true }, "machine_center_is_zero": { "default_value": true },
"machine_nozzle_size": { "default_value": 0.4 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 ;Home all axes (max endstops)\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."}, "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 ;Home all axes (max endstops)\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."},
"machine_end_gcode": { "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG28 ;Home all axes (max endstops)\nM84 ;steppers off\nG90 ;absolute positioning" }, "machine_end_gcode": { "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG28 ;Home all axes (max endstops)\nM84 ;steppers off\nG90 ;absolute positioning" },
@ -35,7 +34,7 @@
"material_initial_print_temperature": { "value": "material_print_temperature" }, "material_initial_print_temperature": { "value": "material_print_temperature" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" }, "material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"travel_avoid_distance": { "default_value": 1, "value": "1" }, "travel_avoid_distance": { "default_value": 1, "value": "1" },
"speed_print" : { "default_value": 60 }, "speed_print" : { "default_value": 70 },
"speed_travel": { "value": "150.0" }, "speed_travel": { "value": "150.0" },
"speed_infill": { "value": "round(speed_print * 1.05, 0)" }, "speed_infill": { "value": "round(speed_print * 1.05, 0)" },
"speed_topbottom": { "value": "round(speed_print * 0.95, 0)" }, "speed_topbottom": { "value": "round(speed_print * 0.95, 0)" },
@ -53,6 +52,9 @@
"top_bottom_thickness": { "default_value": 0.6 }, "top_bottom_thickness": { "default_value": 0.6 },
"support_z_distance": { "value": "layer_height * 2" }, "support_z_distance": { "value": "layer_height * 2" },
"support_bottom_distance": { "value": "layer_height" }, "support_bottom_distance": { "value": "layer_height" },
"support_use_towers" : { "default_value": false } "support_use_towers" : { "default_value": false },
"jerk_wall_0" : { "value": "30" },
"jerk_travel" : { "default_value": 20 },
"acceleration_travel" : { "value": 10000 }
} }
} }

View File

@ -52,9 +52,6 @@
"bottom_thickness": { "bottom_thickness": {
"default_value": 1 "default_value": 1
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"speed_print": { "speed_print": {
"default_value": 75 "default_value": 75
}, },

View File

@ -35,7 +35,6 @@
"machine_depth": { "default_value": 234 }, "machine_depth": { "default_value": 234 },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_nozzle_size": { "default_value": 0.4 },
"machine_head_with_fans_polygon": { "default_value": [[-75, 35], [-75, -18], [18, 35], [18, -18]] }, "machine_head_with_fans_polygon": { "default_value": [[-75, 35], [-75, -18], [18, 35], [18, -18]] },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_max_feedrate_x": { "default_value": 250 }, "machine_max_feedrate_x": { "default_value": 250 },

View File

@ -1074,7 +1074,7 @@
"maximum_value_warning": "top_layers - 1", "maximum_value_warning": "top_layers - 1",
"type": "int", "type": "int",
"value": "0", "value": "0",
"limit_to_extruder": "roofing_extruder_nr", "limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true, "settable_per_mesh": true,
"enabled": "top_layers > 0" "enabled": "top_layers > 0"
}, },
@ -1196,6 +1196,16 @@
"limit_to_extruder": "top_bottom_extruder_nr", "limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"connect_skin_polygons":
{
"label": "Connect Top/Bottom Polygons",
"description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality.",
"type": "bool",
"default_value": false,
"enabled": "top_bottom_pattern == 'concentric'",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"skin_angles": "skin_angles":
{ {
"label": "Top/Bottom Line Directions", "label": "Top/Bottom Line Directions",
@ -1644,7 +1654,18 @@
"type": "bool", "type": "bool",
"default_value": false, "default_value": false,
"value": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'", "value": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
"enabled": "infill_pattern == 'grid' or infill_pattern == 'triangles' or infill_pattern == 'trihexagon' or infill_pattern == 'cubic' or infill_pattern == 'tetrahedral' or infill_pattern == 'quarter_cubic' or infill_pattern == 'cross' or infill_pattern == 'cross_3d'", "enabled": "infill_pattern == 'lines' or infill_pattern == 'grid' or infill_pattern == 'triangles' or infill_pattern == 'trihexagon' or infill_pattern == 'cubic' or infill_pattern == 'tetrahedral' or infill_pattern == 'quarter_cubic' or infill_pattern == 'cross' or infill_pattern == 'cross_3d'",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"connect_infill_polygons":
{
"label": "Connect Infill Polygons",
"description": "Connect infill paths where they run next to each other. For infill patterns which consist of several closed polygons, enabling this setting greatly reduces the travel time.",
"type": "bool",
"default_value": true,
"value": "infill_pattern == 'cross' or infill_pattern == 'cross_3d' or infill_multiplier % 2 == 0",
"enabled": "infill_pattern == 'cross' or infill_pattern == 'cross_3d' or infill_multiplier % 2 == 0",
"limit_to_extruder": "infill_extruder_nr", "limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true "settable_per_mesh": true
}, },
@ -1680,6 +1701,18 @@
"limit_to_extruder": "infill_extruder_nr", "limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"infill_multiplier":
{
"label": "Infill Line Multiplier",
"description": "Convert each infill line to this many lines. The extra lines do not cross over each other, but avoid each other. This makes the infill stiffer, but increases print time and material usage.",
"default_value": 1,
"type": "int",
"minimum_value": "1",
"maximum_value_warning": "infill_line_distance / infill_line_width",
"enabled": "infill_sparse_density > 0 and not spaghetti_infill_enabled and infill_pattern != 'zigzag'",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"sub_div_rad_add": "sub_div_rad_add":
{ {
"label": "Cubic Subdivision Shell", "label": "Cubic Subdivision Shell",
@ -4247,6 +4280,27 @@
} }
} }
}, },
"support_fan_enable":
{
"label": "Fan Speed Override",
"description": "When enabled, the print cooling fan speed is altered for the skin regions immediately above the support.",
"type": "bool",
"default_value": false,
"enabled": "support_enable",
"settable_per_mesh": false
},
"support_supported_skin_fan_speed":
{
"label": "Supported Skin Fan Speed",
"description": "Percentage fan speed to use when printing the skin regions immediately above the support. Using a high fan speed can make the support easier to remove.",
"unit": "%",
"minimum_value": "0",
"maximum_value": "100",
"default_value": 100,
"type": "float",
"enabled": "support_enable and support_fan_enable",
"settable_per_mesh": false
},
"support_use_towers": "support_use_towers":
{ {
"label": "Use Towers", "label": "Use Towers",
@ -4992,32 +5046,12 @@
"description": "The minimum volume for each layer of the prime tower in order to purge enough material.", "description": "The minimum volume for each layer of the prime tower in order to purge enough material.",
"unit": "mm³", "unit": "mm³",
"type": "float", "type": "float",
"default_value": 10, "default_value": 5,
"value": "8.48 if prime_tower_circular else 10",
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "round((resolveOrValue('prime_tower_size') * 0.5) ** 2 * 3.14159 * resolveOrValue('layer_height'), 2) if prime_tower_circular else resolveOrValue('prime_tower_size') ** 2 * resolveOrValue('layer_height')", "maximum_value_warning": "((resolveOrValue('prime_tower_size') * 0.5) ** 2 * 3.14159 * resolveOrValue('layer_height') if prime_tower_circular else resolveOrValue('prime_tower_size') ** 2 * resolveOrValue('layer_height')) - sum(extruderValues('prime_tower_min_volume')) + prime_tower_min_volume",
"enabled": "resolveOrValue('prime_tower_enable')", "enabled": "resolveOrValue('prime_tower_enable')",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true, "settable_per_extruder": true
"children":
{
"prime_tower_wall_thickness":
{
"label": "Prime Tower Thickness",
"description": "The thickness of the hollow prime tower. A thickness larger than half the Prime Tower Minimum Volume will result in a dense prime tower.",
"unit": "mm",
"type": "float",
"default_value": 2,
"value": "round(max(2 * prime_tower_line_width, (0.5 * (prime_tower_size - math.sqrt(max(0, prime_tower_size ** 2 - 4 * prime_tower_min_volume / (3.14159 * layer_height))))) if prime_tower_circular else (0.5 * (prime_tower_size - math.sqrt(max(0, prime_tower_size ** 2 - prime_tower_min_volume / layer_height))))), 3)",
"resolve": "max(extruderValues('prime_tower_wall_thickness'))",
"minimum_value": "0.001",
"minimum_value_warning": "2 * min(extruderValues('prime_tower_line_width')) - 0.0001",
"maximum_value_warning": "prime_tower_size / 2",
"enabled": "prime_tower_enable",
"settable_per_mesh": false,
"settable_per_extruder": false
}
}
}, },
"prime_tower_position_x": "prime_tower_position_x":
{ {
@ -5072,29 +5106,6 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
"dual_pre_wipe":
{
"label": "Wipe Nozzle After Switch",
"description": "After switching extruder, wipe the oozed material off of the nozzle on the first thing printed. This performs a safe slow wipe move at a place where the oozed material causes least harm to the surface quality of your print.",
"type": "bool",
"enabled": "resolveOrValue('prime_tower_enable')",
"default_value": true,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"prime_tower_purge_volume":
{
"label": "Prime Tower Purge Volume",
"description": "Amount of filament to be purged when wiping on the prime tower. Purging is useful for compensating the filament lost by oozing during inactivity of the nozzle.",
"type": "float",
"enabled": "resolveOrValue('prime_tower_enable') and dual_pre_wipe",
"unit": "mm³",
"default_value": 0,
"minimum_value": "0",
"maximum_value_warning": "2.5",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"ooze_shield_enabled": "ooze_shield_enabled":
{ {
"label": "Enable Ooze Shield", "label": "Enable Ooze Shield",
@ -5195,6 +5206,7 @@
"type": "bool", "type": "bool",
"default_value": true, "default_value": true,
"value": "extruders_enabled_count > 1", "value": "extruders_enabled_count > 1",
"enabled": "all(p != 'surface' for p in extruderValues('magic_mesh_surface_mode'))",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true "settable_per_meshgroup": true
@ -5205,7 +5217,7 @@
"description": "Switch to which mesh intersecting volumes will belong with every layer, so that the overlapping meshes become interwoven. Turning this setting off will cause one of the meshes to obtain all of the volume in the overlap, while it is removed from the other meshes.", "description": "Switch to which mesh intersecting volumes will belong with every layer, so that the overlapping meshes become interwoven. Turning this setting off will cause one of the meshes to obtain all of the volume in the overlap, while it is removed from the other meshes.",
"type": "bool", "type": "bool",
"default_value": true, "default_value": true,
"enabled": "carve_multiple_volumes", "enabled": "carve_multiple_volumes and all(p != 'surface' for p in extruderValues('magic_mesh_surface_mode'))",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true "settable_per_meshgroup": true

View File

@ -44,7 +44,6 @@
"retraction_amount": { "default_value": 1 }, "retraction_amount": { "default_value": 1 },
"retraction_speed": { "default_value": 50}, "retraction_speed": { "default_value": 50},
"material_flow": { "default_value": 87 }, "material_flow": { "default_value": 87 },
"machine_nozzle_size": { "default_value": 0.35 },
"adhesion_type": { "default_value": "skirt" }, "adhesion_type": { "default_value": "skirt" },
"skirt_brim_minimal_length": { "default_value": 130}, "skirt_brim_minimal_length": { "default_value": 130},

View File

@ -27,7 +27,6 @@
"machine_depth": { "default_value": 406 }, "machine_depth": { "default_value": 406 },
"machine_height": { "default_value": 533 }, "machine_height": { "default_value": 533 },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"machine_nozzle_size": { "default_value": 0.5 },
"layer_height": { "default_value": 0.2 }, "layer_height": { "default_value": 0.2 },
"layer_height_0": { "default_value": 0.3 }, "layer_height_0": { "default_value": 0.3 },
"retraction_amount": { "default_value": 1 }, "retraction_amount": { "default_value": 1 },

View File

@ -28,7 +28,6 @@
"machine_depth": { "default_value": 406 }, "machine_depth": { "default_value": 406 },
"machine_height": { "default_value": 533 }, "machine_height": { "default_value": 533 },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"machine_nozzle_size": { "default_value": 0.5 },
"layer_height": { "default_value": 0.2 }, "layer_height": { "default_value": 0.2 },
"layer_height_0": { "default_value": 0.3 }, "layer_height_0": { "default_value": 0.3 },
"retraction_amount": { "default_value": 1 }, "retraction_amount": { "default_value": 1 },

View File

@ -29,9 +29,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.5
},
"machine_head_polygon": { "machine_head_polygon": {
"default_value": [ "default_value": [
[-75, -18], [-75, -18],

View File

@ -26,7 +26,6 @@
"machine_width": { "default_value": 170 }, "machine_width": { "default_value": 170 },
"machine_height": { "default_value": 145 }, "machine_height": { "default_value": 145 },
"machine_depth": { "default_value": 160 }, "machine_depth": { "default_value": 160 },
"machine_nozzle_size": { "default_value": 0.4 },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },

View File

@ -32,9 +32,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": true "default_value": true
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_polygon": { "machine_head_polygon": {
"default_value": [ "default_value": [
[-43.7, -19.2], [-43.7, -19.2],

View File

@ -34,9 +34,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_heat_up_speed": { "machine_nozzle_heat_up_speed": {
"default_value": 2 "default_value": 2
}, },

View File

@ -35,9 +35,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_heat_up_speed": { "machine_nozzle_heat_up_speed": {
"default_value": 2 "default_value": 2
}, },

View File

@ -32,9 +32,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": true "default_value": true
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_gcode_flavor": { "machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)" "default_value": "RepRap (Marlin/Sprinter)"
}, },

View File

@ -31,9 +31,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": true "default_value": true
}, },
"machine_nozzle_size": {
"default_value": 0.35
},
"machine_gcode_flavor": { "machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)" "default_value": "RepRap (Marlin/Sprinter)"
}, },

View File

@ -33,9 +33,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_polygon": { "machine_head_polygon": {
"default_value": [ "default_value": [
[-75, -18], [-75, -18],

View File

@ -33,9 +33,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_polygon": { "machine_head_polygon": {
"default_value": [ "default_value": [
[-75, -18], [-75, -18],

View File

@ -30,9 +30,6 @@
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"default_value": [ "default_value": [

Some files were not shown because too many files have changed in this diff Show More