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']) {
timeout(time: 2, unit: "HOURS") {
// Prepare building
stage('Prepare') {
// Ensure we start with a clean build directory.

View File

@ -1,10 +1,13 @@
[Desktop Entry]
Name=Ultimaker Cura
Name[de]=Ultimaker Cura
Name[nl]=Ultimaker Cura
GenericName=3D Printing 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[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
TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura
Icon=cura-icon
@ -12,4 +15,4 @@ Terminal=false
Type=Application
MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;
Categories=Graphics;
Keywords=3D;Printing;
Keywords=3D;Printing;Slicer;

View File

@ -1,12 +1,13 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import io
import os
import re
import shutil
from typing import Optional
from typing import Dict, Optional
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
from UM import i18nCatalog
@ -28,9 +29,9 @@ class Backup:
# Re-use translation catalog.
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.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.
def makeFromCurrent(self) -> None:

View File

@ -1,6 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# 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 cura.Backups.Backup import Backup
@ -18,7 +19,7 @@ class BackupsManager:
## Get a back-up of the current configuration.
# \return A tuple containing a ZipFile (the actual back-up) and a dict
# 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()
backup = Backup()
backup.makeFromCurrent()
@ -30,7 +31,7 @@ class BackupsManager:
# \param zip_file A bytes object containing the actual back-up.
# \param meta_data A dict containing some metadata that is needed to
# 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 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.")
@ -43,13 +44,13 @@ class BackupsManager:
if restored:
# 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.
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
# restoring a back-up.
def _disableAutoSave(self):
def _disableAutoSave(self) -> None:
self._application.setSaveDataEnabled(False)
## Re-enable auto-save after we're done.
def _enableAutoSave(self):
def _enableAutoSave(self) -> None:
self._application.setSaveDataEnabled(True)

View File

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

View File

@ -5,6 +5,7 @@ import copy
import os
import sys
import time
from typing import cast, TYPE_CHECKING, Optional
import numpy
@ -13,8 +14,6 @@ from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
from typing import cast, TYPE_CHECKING
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Camera import Camera
from UM.Math.Vector import Vector
@ -84,7 +83,6 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager
from plugins.SliceInfoPlugin.SliceInfo import SliceInfo
from .SingleInstance import SingleInstance
from .AutoSave import AutoSave
@ -98,6 +96,8 @@ from . import CuraSplashScreen
from . import CameraImageProvider
from . import MachineActionManager
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
from cura.Settings.MachineManager import MachineManager
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel
@ -110,6 +110,10 @@ from cura.ObjectsModel import ObjectsModel
from UM.FlameProfiler import pyqtSlot
if TYPE_CHECKING:
from plugins.SliceInfoPlugin.SliceInfo import SliceInfo
numpy.seterr(all = "ignore")
try:
@ -155,6 +159,8 @@ class CuraApplication(QtApplication):
self._boot_loading_time = time.time()
self._on_exit_callback_manager = OnExitCallbackManager(self)
# Variables set from CLI
self._files_to_open = []
self._use_single_instance = False
@ -276,6 +282,8 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
self._machine_action_manager.initialize()
self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
def __sendCommandToSingleInstance(self):
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.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.empty_definition_changes_container = empty_definition_changes_container
empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant")
empty_variant_container.setMetaDataEntry("type", "variant")
self._container_registry.addContainer(empty_variant_container)
self.empty_variant_container = empty_variant_container
empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material")
empty_material_container.setMetaDataEntry("type", "material")
self._container_registry.addContainer(empty_material_container)
self.empty_material_container = empty_material_container
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
empty_quality_container.setMetaDataEntry("type", "quality")
empty_quality_container.setMetaDataEntry("supported", False)
self._container_registry.addContainer(empty_quality_container)
self.empty_quality_container = empty_quality_container
empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_changes_container.setMetaDataEntry("type", "quality_changes")
empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported")
self._container_registry.addContainer(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.
def startSplashWindowPhase(self):
super().startSplashWindowPhase()
@ -554,8 +525,8 @@ class CuraApplication(QtApplication):
def setNeedToShowUserAgreement(self, set_value = True):
self._need_to_show_user_agreement = set_value
## The "Quit" button click event handler.
@pyqtSlot()
# DO NOT call this function to close the application, use checkAndExitApplication() instead which will perform
# pre-exit checks such as checking for in-progress USB printing, etc.
def closeApplication(self):
Logger.log("i", "Close application")
main_window = self.getMainWindow()
@ -564,6 +535,32 @@ class CuraApplication(QtApplication):
else:
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
showPreferencesWindow = pyqtSignal()
@ -1565,7 +1562,7 @@ class CuraApplication(QtApplication):
self.callLater(self.openProjectFile.emit, file)
return
if Preferences.getInstance().getValue("cura/select_models_on_load"):
if self.getPreferences().getValue("cura/select_models_on_load"):
Selection.clear()
f = file.toLocalFile()
@ -1602,10 +1599,12 @@ class CuraApplication(QtApplication):
def _readMeshFinished(self, job):
nodes = job.getResult()
filename = job.getFileName()
self._currently_loading_files.remove(filename)
file_name = job.getFileName()
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")
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_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:
@ -1638,15 +1637,11 @@ class CuraApplication(QtApplication):
node.scale(original_node.getScale())
node.setSelectable(True)
node.setName(os.path.basename(filename))
node.setName(os.path.basename(file_name))
self.getBuildVolume().checkBoundsAndUpdate(node)
is_non_sliceable = False
filename_lower = filename.lower()
for extension in self._non_sliceable_extensions:
if filename_lower.endswith(extension):
is_non_sliceable = True
break
is_non_sliceable = "." + file_extension in self._non_sliceable_extensions
if is_non_sliceable:
self.callLater(lambda: self.getController().setActiveView("SimulationView"))
@ -1665,7 +1660,7 @@ class CuraApplication(QtApplication):
if not child.getDecorator(ConvexHullDecorator):
child.addDecorator(ConvexHullDecorator())
if arrange_objects_on_load:
if file_extension != "3mf" and arrange_objects_on_load:
if node.callDecoration("isSliceable"):
# 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:
@ -1699,7 +1694,7 @@ class CuraApplication(QtApplication):
if select_models_on_load:
Selection.add(node)
self.fileCompleted.emit(filename)
self.fileCompleted.emit(file_name)
def addNonSliceableExtension(self, extension):
self._non_sliceable_extensions.append(extension)

View File

@ -1,7 +1,11 @@
# Copyright (c) 2018 Ultimaker B.V.
# 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.Settings.GlobalStack import GlobalStack
from UM.PackageManager import PackageManager #The class we're extending.
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)
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.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List
from cura.Machines.MaterialNode import MaterialNode #For type checking.
from typing import List, 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.
# 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:
__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.is_read_only = False
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:
return "%s[%s]" % (self.__class__.__name__, self.name)

View File

@ -378,9 +378,8 @@ class MaterialManager(QObject):
# Look at the guid to material dictionary
root_material_id = None
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"]
break
root_material_id = material_group.root_material_node.metadata["id"]
break
if not root_material_id:
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)

View File

@ -109,6 +109,10 @@ class BrandMaterialsModel(ListModel):
if brand.lower() == "generic":
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:
brand_group_dict[brand] = {}

View File

@ -41,10 +41,15 @@ class GenericMaterialsModel(BaseMaterialsModel):
item_list = []
for root_material_id, container_node in available_material_dict.items():
metadata = container_node.metadata
# Only add results for generic materials
if metadata["brand"].lower() != "generic":
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,
"id": metadata["id"],
"name": metadata["name"],

View File

@ -340,6 +340,13 @@ class QualityManager(QObject):
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
#
@ -459,18 +466,18 @@ class QualityManager(QObject):
# Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(new_id)
quality_changes.setName(new_name)
quality_changes.addMetaDataEntry("type", "quality_changes")
quality_changes.addMetaDataEntry("quality_type", quality_type)
quality_changes.setMetaDataEntry("type", "quality_changes")
quality_changes.setMetaDataEntry("quality_type", quality_type)
# If we are creating a container for an extruder, ensure we add that to the container
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.
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
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

View File

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

View File

@ -5,7 +5,7 @@ import os
import urllib.parse
import uuid
from typing import Any
from typing import Dict, Union
from typing import Dict, Union, Optional
from PyQt5.QtCore import QObject, QUrl, QVariant
from PyQt5.QtWidgets import QMessageBox
@ -47,13 +47,20 @@ class ContainerManager(QObject):
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
@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)
if not metadatas:
Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
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.
#
@ -68,6 +75,7 @@ class ContainerManager(QObject):
#
# \return True if successful, False if not.
# 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)
def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
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.
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)
def makeUniqueName(self, 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 = InstanceContainer(profile_id)
profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion)
profile.addMetaDataEntry("type", "quality_changes")
profile.addMetaDataEntry("definition", expected_machine_definition)
profile.addMetaDataEntry("quality_type", quality_type)
profile.addMetaDataEntry("position", "0")
profile.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion)
profile.setMetaDataEntry("type", "quality_changes")
profile.setMetaDataEntry("definition", expected_machine_definition)
profile.setMetaDataEntry("quality_type", quality_type)
profile.setMetaDataEntry("position", "0")
profile.setDirty(True)
if idx == 0:
# 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_position = str(profile_index - 1)
if not profile.getMetaDataEntry("position"):
profile.addMetaDataEntry("position", extruder_position)
profile.setMetaDataEntry("position", extruder_position)
else:
profile.setMetaDataEntry("position", extruder_position)
profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")
@ -349,7 +349,7 @@ class CuraContainerRegistry(ContainerRegistry):
if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes")
else:
profile.addMetaDataEntry("type", "quality_changes")
profile.setMetaDataEntry("type", "quality_changes")
quality_type = profile.getMetaDataEntry("quality_type")
if not quality_type:
@ -480,16 +480,16 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_stack = ExtruderStack.ExtruderStack(unique_name)
extruder_stack.setName(extruder_definition.getName())
extruder_stack.setDefinition(extruder_definition)
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_stack.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
# 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_name = definition_changes_id
definition_changes = InstanceContainer(definition_changes_id, parent = application)
definition_changes.setName(definition_changes_name)
definition_changes.addMetaDataEntry("setting_version", application.SettingVersion)
definition_changes.addMetaDataEntry("type", "definition_changes")
definition_changes.addMetaDataEntry("definition", extruder_definition.getId())
definition_changes.setMetaDataEntry("setting_version", application.SettingVersion)
definition_changes.setMetaDataEntry("type", "definition_changes")
definition_changes.setMetaDataEntry("definition", extruder_definition.getId())
# move definition_changes settings if exist
for setting_key in definition_changes.getAllKeys():
@ -514,9 +514,9 @@ class CuraContainerRegistry(ContainerRegistry):
user_container_name = user_container_id
user_container = InstanceContainer(user_container_id, parent = application)
user_container.setName(user_container_name)
user_container.addMetaDataEntry("type", "user")
user_container.addMetaDataEntry("machine", machine.getId())
user_container.addMetaDataEntry("setting_version", application.SettingVersion)
user_container.setMetaDataEntry("type", "user")
user_container.setMetaDataEntry("machine", machine.getId())
user_container.setMetaDataEntry("setting_version", application.SettingVersion)
user_container.setDefinition(machine.definition.getId())
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())
if extruder_quality_changes_container:
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]
else:
# 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)
extruder_quality_changes_container = InstanceContainer(container_id, parent = application)
extruder_quality_changes_container.setName(container_name)
extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes")
extruder_quality_changes_container.addMetaDataEntry("setting_version", application.SettingVersion)
extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
extruder_quality_changes_container.setMetaDataEntry("type", "quality_changes")
extruder_quality_changes_container.setMetaDataEntry("setting_version", application.SettingVersion)
extruder_quality_changes_container.setMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
extruder_quality_changes_container.setMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
self.addContainer(extruder_quality_changes_container)

View File

@ -57,7 +57,7 @@ class CuraContainerStack(ContainerStack):
self.containersChanged.connect(self._onContainersChanged)
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.
pyqtContainersChanged = pyqtSignal()

View File

@ -146,7 +146,7 @@ class CuraStackBuilder:
stack.setName(extruder_definition.getName())
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,
is_global_stack = False)
@ -208,11 +208,11 @@ class CuraStackBuilder:
container = InstanceContainer(unique_container_name)
container.setDefinition(definition_id)
container.addMetaDataEntry("type", "user")
container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
container.setMetaDataEntry("type", "user")
container.setMetaDataEntry("setting_version", CuraApplication.SettingVersion)
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
@ -226,8 +226,8 @@ class CuraStackBuilder:
definition_changes_container = InstanceContainer(unique_container_name)
definition_changes_container.setDefinition(container_stack.getBottom().getId())
definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
definition_changes_container.setMetaDataEntry("type", "definition_changes")
definition_changes_container.setMetaDataEntry("setting_version", CuraApplication.SettingVersion)
registry.addContainer(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:
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)
@ -42,7 +42,7 @@ class ExtruderStack(CuraContainerStack):
def setNextStack(self, stack: CuraContainerStack, connect_signals: bool = True) -> None:
super().setNextStack(stack)
stack.addExtruder(self)
self.addMetaDataEntry("machine", stack.id)
self.setMetaDataEntry("machine", stack.id)
# For backward compatibility: Register the extruder with the Extruder Manager
ExtruderManager.getInstance().registerExtruder(self, stack.id)
@ -53,7 +53,7 @@ class ExtruderStack(CuraContainerStack):
def setEnabled(self, enabled: bool) -> None:
if "enabled" not in self._metadata:
self.addMetaDataEntry("enabled", "True")
self.setMetaDataEntry("enabled", "True")
self.setMetaDataEntry("enabled", str(enabled))
self.enabledChanged.emit()
@ -138,7 +138,7 @@ class ExtruderStack(CuraContainerStack):
def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
super().deserialize(contents, file_name)
if "enabled" not in self.getMetaData():
self.addMetaDataEntry("enabled", "True")
self.setMetaDataEntry("enabled", "True")
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
if stacks:
self.setNextStack(stacks[0])

View File

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

View File

@ -985,6 +985,11 @@ class MachineManager(QObject):
self.updateDefaultExtruder()
self.updateNumberExtrudersEnabled()
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
self._updateQualityWithMaterial()
self.extruderChanged.emit()
@ -1299,9 +1304,9 @@ class MachineManager(QObject):
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
if not new_machine:
return
new_machine.addMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
new_machine.addMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
new_machine.addMetaDataEntry("hidden", False)
new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
new_machine.setMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
new_machine.setMetaDataEntry("hidden", False)
else:
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey)
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)
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")
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)
self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
@ -1434,8 +1444,22 @@ class MachineManager(QObject):
quality_group = quality_group_dict[quality_type]
self.setQualityGroup(quality_group)
## Optionally provide global_stack if you want to use your own
# The active global_stack is treated differently.
@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()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
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.setDirty(False) # This stack does not need to be saved.
user_container = InstanceContainer(container_id = self._generateUniqueName())
user_container.addMetaDataEntry("type", "user")
user_container.setMetaDataEntry("type", "user")
self._stack.userChanges = user_container
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",
add_help = False)
parser.add_argument('--debug',
action='store_true',
parser.add_argument("--debug",
action="store_true",
default = False,
help = "Turn on the debug mode by setting this option."
)
parser.add_argument('--trigger-early-crash',
dest = 'trigger_early_crash',
action = 'store_true',
parser.add_argument("--trigger-early-crash",
dest = "trigger_early_crash",
action = "store_true",
default = False,
help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog."
)
@ -131,6 +131,7 @@ faulthandler.enable(all_threads = True)
# first seems to prevent Sip from going into a state where it
# tries to create PyQt objects on a non-main thread.
import Arcus #@UnusedImport
import Savitar #@UnusedImport
from cura.CuraApplication import CuraApplication
app = CuraApplication()

View File

@ -59,7 +59,7 @@ class ThreeMFReader(MeshReader):
if transformation == "":
return Matrix()
splitted_transformation = transformation.split()
split_transformation = transformation.split()
## Transformation is saved as:
## M00 M01 M02 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!
temp_mat = Matrix()
# Rotation & Scale
temp_mat._data[0, 0] = splitted_transformation[0]
temp_mat._data[1, 0] = splitted_transformation[1]
temp_mat._data[2, 0] = splitted_transformation[2]
temp_mat._data[0, 1] = splitted_transformation[3]
temp_mat._data[1, 1] = splitted_transformation[4]
temp_mat._data[2, 1] = splitted_transformation[5]
temp_mat._data[0, 2] = splitted_transformation[6]
temp_mat._data[1, 2] = splitted_transformation[7]
temp_mat._data[2, 2] = splitted_transformation[8]
temp_mat._data[0, 0] = split_transformation[0]
temp_mat._data[1, 0] = split_transformation[1]
temp_mat._data[2, 0] = split_transformation[2]
temp_mat._data[0, 1] = split_transformation[3]
temp_mat._data[1, 1] = split_transformation[4]
temp_mat._data[2, 1] = split_transformation[5]
temp_mat._data[0, 2] = split_transformation[6]
temp_mat._data[1, 2] = split_transformation[7]
temp_mat._data[2, 2] = split_transformation[8]
# Translation
temp_mat._data[0, 3] = splitted_transformation[9]
temp_mat._data[1, 3] = splitted_transformation[10]
temp_mat._data[2, 3] = splitted_transformation[11]
temp_mat._data[0, 3] = split_transformation[9]
temp_mat._data[1, 3] = split_transformation[10]
temp_mat._data[2, 3] = split_transformation[11]
return temp_mat
@ -169,8 +169,6 @@ class ThreeMFReader(MeshReader):
archive = zipfile.ZipFile(file_name, "r")
self._base_name = os.path.basename(file_name)
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())
self._unit = scene_3mf.getUnit()
for node in scene_3mf.getSceneNodes():

View File

@ -963,7 +963,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if not extruder_info:
continue
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))
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]

View File

@ -21,6 +21,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.SettingInstance import SettingInstance #For typing.
from UM.Tool import Tool #For typing.
from UM.Mesh.MeshData import MeshData #For typing.
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
@ -62,15 +63,15 @@ class CuraEngineBackend(QObject, Backend):
if Platform.isLinux() and not default_engine_location:
if not os.getenv("PATH"):
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)
if os.path.exists(execpath):
default_engine_location = execpath
break
self._application = CuraApplication.getInstance() #type: CuraApplication
self._multi_build_plate_model = None #type: MultiBuildPlateModel
self._machine_error_checker = None #type: MachineErrorChecker
self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel]
self._machine_error_checker = None #type: Optional[MachineErrorChecker]
if not default_engine_location:
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._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._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._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._onGlobalStackChanged()
@ -246,7 +249,7 @@ class CuraEngineBackend(QObject, Backend):
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
if self._process is None:
if self._process is None: # type: ignore
self._createSocket()
self.stopSlicing()
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():
return
if self._process is not None:
if self._process is not None: # type: ignore
Logger.log("d", "Killing engine process")
try:
self._process.terminate()
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait())
self._process = None
self._process.terminate() # type: ignore
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) # type: ignore
self._process = None # type: ignore
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))
@ -328,6 +331,9 @@ class CuraEngineBackend(QObject, Backend):
if job.getResult() == StartJobResult.SettingError:
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()))
error_keys = [] #type: List[str]
for extruder in extruders:
@ -361,6 +367,9 @@ class CuraEngineBackend(QObject, Backend):
if not stack:
continue
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)
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))
@ -409,7 +418,8 @@ class CuraEngineBackend(QObject, Backend):
# Notify the user that it's now up to the backend to do it's job
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.
# It disables when
@ -447,7 +457,8 @@ class CuraEngineBackend(QObject, Backend):
# Only count sliceable objects
if node.callDecoration("isSliceable"):
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
## Listener for when the scene has changed.
@ -476,15 +487,14 @@ class CuraEngineBackend(QObject, Backend):
else:
# we got a single scenenode
if not source.callDecoration("isGroup"):
if source.getMeshData() is None:
return
if source.getMeshData().getVertices() is None:
mesh_data = source.getMeshData()
if mesh_data is None or mesh_data.getVertices() is None:
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:
return
@ -524,6 +534,11 @@ class CuraEngineBackend(QObject, Backend):
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")
# _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)
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.
@ -577,9 +592,10 @@ class CuraEngineBackend(QObject, Backend):
#
# \param message The protobuf message containing sliced layer data.
def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None:
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] = []
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
if self._start_slice_job_build_plate is not None:
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] = []
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
## Called when a progress message is received from the engine.
#
@ -619,7 +635,8 @@ class CuraEngineBackend(QObject, Backend):
gcode_list[index] = replaced
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()))
# See if we need to process the sliced layers job.
@ -658,7 +675,11 @@ class CuraEngineBackend(QObject, Backend):
## Creates a new socket connection.
def _createSocket(self, protocol_file: str = None) -> None:
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)
self._engine_is_fresh = True
@ -773,9 +794,9 @@ class CuraEngineBackend(QObject, Backend):
# We should reset our state and start listening for new connections.
def _onBackendQuit(self) -> None:
if not self._restart:
if self._process:
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait())
self._process = None
if self._process: # type: ignore
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait()) # type: ignore
self._process = None # type: ignore
## Called when the global container stack changes
def _onGlobalStackChanged(self) -> None:
@ -831,6 +852,9 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.start()
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):
if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number)

View File

@ -5,7 +5,7 @@ import numpy
from string import Formatter
from enum import IntEnum
import time
from typing import Any, Dict, List, Optional, Set
from typing import Any, cast, Dict, List, Optional, Set
import re
import Arcus #For typing.
@ -41,39 +41,32 @@ class StartJobResult(IntEnum):
## Formatter class that handles token expansion in start/end gcode
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),
# 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:
extruder_nr = int(kwargs["default_extruder_nr"])
extruder_nr = int(key_fragments[1])
except ValueError:
extruder_nr = -1
key_fragments = [fragment.strip() for fragment in key.split(",")]
if len(key_fragments) == 2:
try:
extruder_nr = int(key_fragments[1])
except ValueError:
try:
extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack
except (KeyError, ValueError):
# 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:
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 (KeyError, ValueError):
# 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) + "}"
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.
@ -216,12 +209,15 @@ class StartSliceJob(Job):
if 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 = []
has_model_with_disabled_extruders = False
associated_disabled_extruders = set()
for group in object_groups:
stack = CuraApplication.getInstance().getGlobalContainerStack()
stack = global_stack
skip_group = False
for node in group:
extruder_position = node.callDecoration("getActiveExtruderPosition")
@ -234,7 +230,7 @@ class StartSliceJob(Job):
if has_model_with_disabled_extruders:
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))
return
@ -294,6 +290,9 @@ class StartSliceJob(Job):
def isCancelled(self) -> bool:
return self._is_cancelled
def setIsCancelled(self, value: bool):
self._is_cancelled = value
## Creates a dictionary of tokens to replace in g-code pieces.
#
# 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
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
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
self._all_extruders_settings = {

View File

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

View File

@ -286,6 +286,10 @@ class FlavorParser:
self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths
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")
scene_node = CuraSceneNode()

View File

@ -127,11 +127,11 @@ class GCodeWriter(MeshWriter):
flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
# If the quality changes is not set, we need to set type manually
if flat_global_container.getMetaDataEntry("type", None) is None:
flat_global_container.addMetaDataEntry("type", "quality_changes")
flat_global_container.setMetaDataEntry("type", "quality_changes")
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
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
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
@ -151,15 +151,15 @@ class GCodeWriter(MeshWriter):
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
# If the quality changes is not set, we need to set type manually
if flat_extruder_quality.getMetaDataEntry("type", None) is None:
flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
flat_extruder_quality.setMetaDataEntry("type", "quality_changes")
# Ensure that extruder is set. (Can happen if we have empty quality changes).
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).
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
flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality)

View File

@ -145,9 +145,9 @@ class LegacyProfileReader(ProfileReader):
if len(profile.getAllKeys()) == 0:
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
profile.addMetaDataEntry("quality_type", "normal")
profile.setMetaDataEntry("quality_type", "normal")
profile.setName(profile_id)
profile.setDirty(True)

View File

@ -135,10 +135,7 @@ class MachineSettingsAction(MachineAction):
material_node = None
if has_materials:
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.setMetaDataEntry("has_materials", True)
else:
self._global_container_stack.addMetaDataEntry("has_materials", True)
self._global_container_stack.setMetaDataEntry("has_materials", True)
else:
# 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.

View File

@ -248,7 +248,7 @@ class PostProcessingPlugin(QObject, Extension):
global_stack = Application.getInstance().getGlobalContainerStack()
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)
## 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._instance = InstanceContainer(container_id="ScriptInstanceContainer")
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.propertyChanged.connect(self._onPropertyChanged)
@ -105,9 +105,12 @@ class Script:
if m is None:
return default
try:
return float(m.group(0))
except:
return default
return int(m.group(0))
except ValueError: #Not an integer.
try:
return float(m.group(0))
except ValueError: #Not a number at all.
return default
## Convenience function to produce a line of g-code.
#

View File

@ -58,10 +58,10 @@ class FilamentChange(Script):
color_change = "M600"
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.:
color_change = color_change + (" L%.2f" % later_retract)
color_change = color_change + (" L-%.2f" % later_retract)
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 cura.Settings.ExtruderManager import ExtruderManager
from UM.Application import Application #To get the current printer's settings.
class PauseAtHeight(Script):
def __init__(self):
@ -136,6 +140,10 @@ class PauseAtHeight(Script):
layers_started = False
redo_layers = self.getSettingValueByKey("redo_layers")
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")
@ -153,6 +161,8 @@ class PauseAtHeight(Script):
# Scroll each line of instruction for each layer in the G-code
for line in lines:
if ";FLAVOR:Griffin" in line:
is_griffin = True
# Fist positive layer reached
if ";LAYER:0" in line:
layers_started = True
@ -162,12 +172,12 @@ class PauseAtHeight(Script):
#Track the latest printing temperature in order to resume at the correct temperature.
if line.startswith("T"):
current_t = int(self.getValue(line, "T"))
current_t = self.getValue(line, "T")
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
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")
if not layers_started:
@ -247,57 +257,71 @@ class PauseAtHeight(Script):
prepend_gcode += ";added code by post processing\n"
prepend_gcode += ";script: PauseAtHeight.py\n"
if pause_at == "height":
prepend_gcode += ";current z: {z}\n".format(z=current_z)
prepend_gcode += ";current height: {height}\n".format(height=current_height)
prepend_gcode += ";current z: {z}\n".format(z = current_z)
prepend_gcode += ";current height: {height}\n".format(height = current_height)
else:
prepend_gcode += ";current layer: {layer}\n".format(layer=current_layer)
prepend_gcode += ";current layer: {layer}\n".format(layer = current_layer)
# Retraction
prepend_gcode += self.putValue(M=83) + "\n"
if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n"
if not is_griffin:
# Retraction
prepend_gcode += self.putValue(M = 83) + "\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
prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n"
# Move the head away
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
# This line should be ok
prepend_gcode += self.putValue(G=1, X=park_x, Y=park_y, F=9000) + "\n"
# This line should be ok
prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n"
if current_z < 15:
prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n"
if current_z < 15:
prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n"
# Set extruder standby temperature
prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n"
if control_temperatures:
# Set extruder standby temperature
prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n"
# 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
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n"
if not is_griffin:
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,
if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n"
# Push the filament back,
if retraction_amount != 0:
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
# Optionally extrude material
if extrude_amount != 0:
prepend_gcode += self.putValue(G=1, E=extrude_amount, F=extrude_speed * 60) + "\n"
# Optionally extrude material
if extrude_amount != 0:
prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n"
# and retract again, the properly primes the nozzle
# when changing filament.
if retraction_amount != 0:
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n"
# and retract again, the properly primes the nozzle
# when changing filament.
if retraction_amount != 0:
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head back
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"
if retraction_amount != 0:
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"
# Move the head back
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"
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 = 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
prepend_gcode += self.putValue(G=92, E=current_e) + "\n"
# reset extrude value to pre pause value
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
layer = prepend_gcode + layer

View File

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

View File

@ -11,7 +11,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
# 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
# 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.
# 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.DWORD, # _In_ DWORD dwIoControlCode
wintypes.LPVOID, # _In_opt_ LPVOID lpInBuffer
@ -39,7 +39,7 @@ ctypes.windll.kernel32.DeviceIoControl.argtypes = [
ctypes.POINTER(wintypes.DWORD), # _Out_opt_ LPDWORD lpBytesReturned
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

View File

@ -16,7 +16,7 @@ from UM.i18n import i18nCatalog
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.Qt.Duration import DurationFormat
from typing import cast, Optional
from .SliceInfoJob import SliceInfoJob
@ -79,11 +79,16 @@ class SliceInfo(QObject, Extension):
return dialog
@pyqtSlot(result = str)
def getExampleData(self) -> str:
def getExampleData(self) -> Optional[str]:
if self._example_data_content is None:
file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "example_data.json")
with open(file_path, "r", encoding = "utf-8") as f:
self._example_data_content = f.read()
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if not plugin_path:
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
@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
{
role: "machine"
title: "Machine"
width: Math.floor(table.width * 0.25)
delegate: columnTextDelegate
}
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)
width: childrenRect.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@label", "Version") + ":"
@ -110,6 +111,7 @@ Item
topMargin: UM.Theme.getSize("default_margin").height
}
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
height: childrenRect.height
Label
{
text: details.version || catalog.i18nc("@label", "Unknown")

View File

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

View File

@ -9,6 +9,8 @@ import UM 1.1 as UM
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
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Rectangle
@ -40,6 +42,21 @@ Item
source: model.icon_url || "../images/logobot.svg"
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
{
@ -87,8 +104,18 @@ Item
switch(toolbox.viewCategory)
{
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
default:
toolbox.viewPage = "detail"

View File

@ -29,6 +29,17 @@ ScrollView
{
id: allPlugins
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
{
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
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)
@ -36,6 +38,22 @@ Rectangle
source: model.icon_url || "../images/logobot.svg"
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
{

View File

@ -15,6 +15,7 @@ Item
Label
{
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)
verticalAlignment: Text.AlignVCenter
anchors
@ -25,6 +26,7 @@ Item
right: restartButton.right
rightMargin: UM.Theme.getSize("default_margin").width
}
}
Button
{

View File

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

View File

@ -43,7 +43,7 @@ class AuthorsModel(ListModel):
"package_count": author["package_count"] if "package_count" in author else 0,
"package_types": author["package_types"] if "package_types" in author else [],
"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.

View File

@ -33,6 +33,7 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 16, "has_configs")
self.addRoleName(Qt.UserRole + 17, "supported_configs")
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.
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,
"has_configs": has_configs,
"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.
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)
else:
key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value)

View File

@ -6,7 +6,7 @@ import json
import os
import tempfile
import platform
from typing import List
from typing import cast, List
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
@ -71,7 +71,8 @@ class Toolbox(QObject, Extension):
"plugins_installed": [],
"materials_showcase": [],
"materials_available": [],
"materials_installed": []
"materials_installed": [],
"materials_generic": []
} # type: Dict[str, List[Any]]
# Models:
@ -83,7 +84,8 @@ class Toolbox(QObject, Extension):
"plugins_installed": PackagesModel(self),
"materials_showcase": AuthorsModel(self),
"materials_available": PackagesModel(self),
"materials_installed": PackagesModel(self)
"materials_installed": PackagesModel(self),
"materials_generic": PackagesModel(self)
} # type: Dict[str, ListModel]
# 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._dialog = None #type: Optional[QObject]
self._confirm_reset_dialog = None #type: Optional[QObject]
self._resetUninstallVariables()
self._restart_required = False #type: bool
# variables for the license agreement dialog
@ -130,6 +135,13 @@ class Toolbox(QObject, Extension):
filterChanged = pyqtSignal()
metadataChanged = 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)
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_available": QUrl("{base_url}/packages?package_type=plugin".format(base_url=self._api_url)),
"materials_showcase": QUrl("{base_url}/showcase".format(base_url=self._api_url)),
"materials_available": QUrl("{base_url}/packages?package_type=material".format(base_url=self._api_url))
"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.
@ -218,12 +231,19 @@ class Toolbox(QObject, Extension):
self._makeRequestByType("authors")
self._makeRequestByType("plugins_showcase")
self._makeRequestByType("materials_showcase")
self._makeRequestByType("materials_available")
self._makeRequestByType("materials_generic")
# Gather installed packages:
self._updateInstalledModels()
if not self._dialog:
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()
# Apply enabled/disabled state to installed plugins
@ -231,11 +251,16 @@ class Toolbox(QObject, Extension):
def _createDialog(self, qml_name: str) -> Optional[QObject]:
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})
if not dialog:
raise Exception("Failed to create toolbox dialog")
return dialog
def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]:
formatted = {
"package_id": plugin["id"],
@ -294,9 +319,90 @@ class Toolbox(QObject, Extension):
self._restart_required = True
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)
def uninstall(self, plugin_id: str) -> None:
self._package_manager.removePackage(plugin_id, force_add = True)
def checkPackageUsageAndUninstall(self, package_id: str) -> None:
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._updateInstalledModels()
self.metadataChanged.emit()
@ -400,6 +506,22 @@ class Toolbox(QObject, Extension):
def isInstalled(self, package_id: str) -> bool:
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)
def isEnabled(self, package_id: str) -> bool:
if package_id in self._plugin_registry.getActivePlugins():
@ -522,6 +644,8 @@ class Toolbox(QObject, Extension):
self._models[type].setFilter({"type": "plugin"})
if type is "authors":
self._models[type].setFilter({"package_types": "material"})
if type is "materials_generic":
self._models[type].setFilter({"tags": "generic"})
self.metadataChanged.emit()
@ -621,27 +745,31 @@ class Toolbox(QObject, Extension):
# --------------------------------------------------------------------------
@pyqtProperty(QObject, notify = metadataChanged)
def authorsModel(self) -> AuthorsModel:
return self._models["authors"]
return cast(AuthorsModel, self._models["authors"])
@pyqtProperty(QObject, notify = metadataChanged)
def packagesModel(self) -> PackagesModel:
return self._models["packages"]
return cast(PackagesModel, self._models["packages"])
@pyqtProperty(QObject, notify = metadataChanged)
def pluginsShowcaseModel(self) -> PackagesModel:
return self._models["plugins_showcase"]
return cast(PackagesModel, self._models["plugins_showcase"])
@pyqtProperty(QObject, notify = metadataChanged)
def pluginsInstalledModel(self) -> PackagesModel:
return self._models["plugins_installed"]
return cast(PackagesModel, self._models["plugins_installed"])
@pyqtProperty(QObject, notify = metadataChanged)
def materialsShowcaseModel(self) -> PackagesModel:
return self._models["materials_showcase"]
def materialsShowcaseModel(self) -> AuthorsModel:
return cast(AuthorsModel, self._models["materials_showcase"])
@pyqtProperty(QObject, notify = metadataChanged)
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.
# 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.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.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from UM.Mesh.MeshWriter import MeshWriter # For typing
from UM.Message import Message
from UM.Qt.Duration import Duration, DurationFormat
from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
@ -103,8 +104,13 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
else:
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
#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]
#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"):
@ -125,7 +131,14 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
else:
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.
if not writer:
Logger.log("e", "Missing file or mesh writer!")
return
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
self._sending_job.send(None) #Start the generator.
@ -205,14 +218,14 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
yield #To prevent having to catch the StopIteration exception.
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,
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.actionTriggered.connect(self._progressMessageActionTriggered)
self._progress_message.show()
parts = []
target_printer, preferred_format, stream = self._dummy_lambdas
@ -249,7 +262,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self.activePrinterChanged.emit()
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
self._progress_message.hide()
if self._progress_message:
self._progress_message.hide()
self._compressing_gcode = False
self._sending_gcode = False

View File

@ -19,5 +19,5 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
def setJobState(self, job: "PrintJobOutputModel", state: str):
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 time
from typing import Optional
from typing import cast, Optional
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
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
else:
global_container_stack.addMetaDataEntry("connect_group_name", group_name)
global_container_stack.addMetaDataEntry("hidden", False)
global_container_stack.setMetaDataEntry("connect_group_name", group_name)
# 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:
# Ensure that the connection states are refreshed.
@ -130,7 +131,7 @@ class DiscoverUM3Action(MachineAction):
global_container_stack.removeMetaDataEntry("network_authentication_key")
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
else:
global_container_stack.addMetaDataEntry("um_network_key", key)
global_container_stack.setMetaDataEntry("um_network_key", key)
if self._network_plugin:
# Ensure that the connection states are refreshed.
@ -170,7 +171,10 @@ class DiscoverUM3Action(MachineAction):
Logger.log("d", "Creating additional ui components for UM3.")
# 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})
if not self.__additional_components_view:
Logger.log("w", "Could not create ui components for UM3.")

View File

@ -165,7 +165,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
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:
# 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
self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
onFinished=self._onPostPrintJobFinished)
on_finished=self._onPostPrintJobFinished)
return
@ -381,8 +381,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self._checkAuthentication()
# 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("print_job", onFinished=self._onGetPrintJobFinished)
self.get("printer", on_finished=self._onGetPrinterDataFinished)
self.get("print_job", on_finished=self._onGetPrintJobFinished)
def _resetAuthenticationRequestedMessage(self):
if self._authentication_requested_message:
@ -404,7 +404,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def _verifyAuthentication(self):
Logger.log("d", "Attempting to verify authentication")
# 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):
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
@ -426,7 +426,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def _checkAuthentication(self):
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):
if str(self._authentication_id) not in reply.url().toString():
@ -455,18 +455,18 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self.setAuthenticationState(AuthState.AuthenticationDenied)
self._authentication_failed_message.show()
def _saveAuthentication(self):
def _saveAuthentication(self) -> None:
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 "network_authentication_key" in global_container_stack.getMetaData():
global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
else:
global_container_stack.addMetaDataEntry("network_authentication_key", self._authentication_key)
global_container_stack.setMetaDataEntry("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)
else:
global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id)
# Force save so we are sure the data is not lost.
CuraApplication.getInstance().saveStack(global_container_stack)
@ -502,7 +502,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self.post("auth/request",
json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
"user": self._getUserName()}).encode(),
onFinished=self._onRequestAuthenticationFinished)
on_finished=self._onRequestAuthenticationFinished)
self.setAuthenticationState(AuthState.AuthenticationRequested)
@ -637,4 +637,4 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
result = "********" + 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):
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):
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):
if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
@ -51,10 +51,10 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
new_y = head_pos.y + y
new_z = head_pos.z + 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):
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):
self.setTargetBedTemperature(self._preheat_printer, 0)
@ -89,7 +89,7 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
printer.updateIsPreheating(True)
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)
self._preheat_request_in_progress = True

View File

@ -26,6 +26,10 @@ UM.Dialog
{
resetPrintersModel()
}
else
{
OutputDevice.cancelPrintSelection()
}
}
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.
class SendMaterialJob(Job):
def __init__(self, device: "ClusterUM3OutputDevice"):
def __init__(self, device: "ClusterUM3OutputDevice") -> None:
super().__init__()
self.device = device #type: ClusterUM3OutputDevice

View File

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

View File

@ -88,6 +88,25 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_received = Event()
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
#
def resetDeviceSettings(self):
@ -304,7 +323,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._firmware_name is None:
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)
# Update all temperature values
matched_extruder_nrs = []
@ -423,7 +442,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
elapsed_time = int(time() - self._print_start_time)
print_job = self._printers[0].activePrintJob
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")
self._printers[0].updateActivePrintJob(print_job)

View File

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

View File

@ -6,6 +6,55 @@ import io
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
# state they should be in at version 4.0.
@ -53,6 +102,10 @@ class VersionUpgrade34to40(VersionUpgrade):
parser["general"]["version"] = "4"
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()
parser.write(result)
return [filename], [result.getvalue()]
@ -68,6 +121,11 @@ class VersionUpgrade34to40(VersionUpgrade):
parser["metadata"]["setting_version"] = "5"
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()
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.
from math import pi, sin, cos, sqrt
from typing import Dict
import numpy
@ -42,7 +43,7 @@ class X3DReader(MeshReader):
def __init__(self) -> None:
super().__init__()
self._supported_extensions = [".x3d"]
self._namespaces = {}
self._namespaces = {} # type: Dict[str, str]
# Main entry point
# 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": {
"package_info": {
"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": {
"package_info": {
"package_id": "DagomaChromatikPLA",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
"has_variants": true,
"variants_name": "Tool",
"preferred_variant_name": "0.8 mm",
"preferred_variant_name": "0.8mm thermoplastic extruder",
"preferred_material": "generic_pla",
"preferred_quality_type": "normal",
@ -44,7 +44,7 @@
"material_print_temp_wait": { "default_value": false },
"material_bed_temp_wait": { "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_position_x": { "value": 125 },
"prime_tower_position_y": { "value": 70 },

View File

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

View File

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

View File

@ -9,6 +9,8 @@
"file_formats": "text/x-gcode",
"platform": "discoeasy200.stl",
"platform_offset": [ 105, -59, 280],
"has_machine_quality": true,
"has_materials": true,
"machine_extruder_trains":
{
"0": "dagoma_discoeasy200_extruder_0"
@ -27,37 +29,46 @@
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_with_fans_polygon": {
"default_value": [
[17, 70],
[17, -40],
[-17, -40],
[17, 70]
[-17, -70],
[-17, 40],
[17, 40],
[17, -70]
]
},
"gantry_height": {
"default_value": 10
},
"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": {
"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": {
"default_value": 60
},
"speed_travel": {
"value": "100"
"default_value": 100
},
"retraction_amount": {
"default_value": 3.5
},
"retraction_speed": {
"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",
"version": 2,
"inherits": "fdmprinter",
@ -10,6 +9,8 @@
"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_extruder_0"
@ -28,15 +29,12 @@
"machine_center_is_zero": {
"default_value": true
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_with_fans_polygon": {
"default_value": [
[17, 40],
[17, -70],
[-17, -70],
[17, 40]
[-36, -42],
[-36, 42],
[36, 42],
[36, -42]
]
},
"gantry_height": {
@ -46,14 +44,17 @@
"default_value": "elliptic"
},
"machine_gcode_flavor": {
"default_value": "RepRap (RepRap)"
"default_value": "RepRap"
},
"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": {
"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
},
@ -65,6 +66,15 @@
},
"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

@ -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_topbottom": { "default_value": 30 },
"layer_height": { "default_value": 0.2 },
"machine_nozzle_size": { "default_value": 0.5 },
"speed_print": { "default_value": 30 },
"speed_infill": { "default_value": 30 },
"machine_extruder_count": { "default_value": 1 },

View File

@ -23,7 +23,6 @@
"machine_height": { "default_value": 250 },
"machine_depth": { "default_value": 190 },
"machine_center_is_zero": { "default_value": true },
"machine_nozzle_size": { "default_value": 0.4 },
"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_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_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"travel_avoid_distance": { "default_value": 1, "value": "1" },
"speed_print" : { "default_value": 60 },
"speed_print" : { "default_value": 70 },
"speed_travel": { "value": "150.0" },
"speed_infill": { "value": "round(speed_print * 1.05, 0)" },
"speed_topbottom": { "value": "round(speed_print * 0.95, 0)" },
@ -53,6 +52,9 @@
"top_bottom_thickness": { "default_value": 0.6 },
"support_z_distance": { "value": "layer_height * 2" },
"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": {
"default_value": 1
},
"machine_nozzle_size": {
"default_value": 0.4
},
"speed_print": {
"default_value": 75
},

View File

@ -35,7 +35,6 @@
"machine_depth": { "default_value": 234 },
"machine_center_is_zero": { "default_value": false },
"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_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_max_feedrate_x": { "default_value": 250 },

View File

@ -1074,7 +1074,7 @@
"maximum_value_warning": "top_layers - 1",
"type": "int",
"value": "0",
"limit_to_extruder": "roofing_extruder_nr",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true,
"enabled": "top_layers > 0"
},
@ -1196,6 +1196,16 @@
"limit_to_extruder": "top_bottom_extruder_nr",
"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":
{
"label": "Top/Bottom Line Directions",
@ -1644,7 +1654,18 @@
"type": "bool",
"default_value": false,
"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",
"settable_per_mesh": true
},
@ -1680,6 +1701,18 @@
"limit_to_extruder": "infill_extruder_nr",
"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":
{
"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":
{
"label": "Use Towers",
@ -4992,32 +5046,12 @@
"description": "The minimum volume for each layer of the prime tower in order to purge enough material.",
"unit": "mm³",
"type": "float",
"default_value": 10,
"value": "8.48 if prime_tower_circular else 10",
"default_value": 5,
"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')",
"settable_per_mesh": false,
"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
}
}
"settable_per_extruder": true
},
"prime_tower_position_x":
{
@ -5072,29 +5106,6 @@
"settable_per_mesh": false,
"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":
{
"label": "Enable Ooze Shield",
@ -5195,6 +5206,7 @@
"type": "bool",
"default_value": true,
"value": "extruders_enabled_count > 1",
"enabled": "all(p != 'surface' for p in extruderValues('magic_mesh_surface_mode'))",
"settable_per_mesh": false,
"settable_per_extruder": false,
"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.",
"type": "bool",
"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_extruder": false,
"settable_per_meshgroup": true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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