Merge branch 'master' into feature_local_container_server

Contributes to issue CURA-4243.
This commit is contained in:
Ghostkeeper 2017-11-29 13:06:08 +01:00
commit dad99f5292
No known key found for this signature in database
GPG key ID: 5252B696FB5E7C7A
542 changed files with 35309 additions and 16367 deletions

2
.gitignore vendored
View file

@ -43,6 +43,8 @@ plugins/ProfileFlattener
plugins/cura-god-mode-plugin plugins/cura-god-mode-plugin
plugins/cura-big-flame-graph plugins/cura-big-flame-graph
plugins/cura-siemensnx-plugin plugins/cura-siemensnx-plugin
plugins/CuraVariSlicePlugin
plugins/CuraLiveScriptingPlugin
#Build stuff #Build stuff
CMakeCache.txt CMakeCache.txt

View file

@ -39,7 +39,7 @@ find_package(PythonInterp 3.5.0 REQUIRED)
install(DIRECTORY resources install(DIRECTORY resources
DESTINATION ${CMAKE_INSTALL_DATADIR}/cura) DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
install(DIRECTORY plugins install(DIRECTORY plugins
DESTINATION lib/cura) DESTINATION lib${LIB_SUFFIX}/cura)
if(NOT APPLE AND NOT WIN32) if(NOT APPLE AND NOT WIN32)
install(FILES cura_app.py install(FILES cura_app.py
DESTINATION ${CMAKE_INSTALL_BINDIR} DESTINATION ${CMAKE_INSTALL_BINDIR}
@ -47,16 +47,16 @@ if(NOT APPLE AND NOT WIN32)
RENAME cura) RENAME cura)
if(EXISTS /etc/debian_version) if(EXISTS /etc/debian_version)
install(DIRECTORY cura install(DIRECTORY cura
DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}/dist-packages
FILES_MATCHING PATTERN *.py) FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib/python${PYTHON_VERSION_MAJOR}/dist-packages/cura) DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}/dist-packages/cura)
else() else()
install(DIRECTORY cura install(DIRECTORY cura
DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages
FILES_MATCHING PATTERN *.py) FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura)
endif() endif()
install(FILES ${CMAKE_BINARY_DIR}/cura.desktop install(FILES ${CMAKE_BINARY_DIR}/cura.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
@ -72,8 +72,8 @@ else()
DESTINATION ${CMAKE_INSTALL_BINDIR} DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY cura install(DIRECTORY cura
DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages
FILES_MATCHING PATTERN *.py) FILES_MATCHING PATTERN *.py)
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) DESTINATION lib${LIB_SUFFIX}/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura)
endif() endif()

View file

@ -20,6 +20,8 @@ For crashes and similar issues, please attach the following information:
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
For additional support, you could also ask in the #cura channel on FreeNode IRC. For help with development, there is also the #cura-dev channel.
Dependencies Dependencies
------------ ------------

View file

@ -876,15 +876,6 @@ class BuildVolume(SceneNode):
return result return result
## Private convenience function to get a setting from the adhesion
# extruder.
#
# \param setting_key The key of the setting to get.
# \param property The property to get from the setting.
# \return The property of the specified setting in the adhesion extruder.
def _getSettingFromAdhesionExtruder(self, setting_key, property = "value"):
return self._getSettingFromExtruder(setting_key, "adhesion_extruder_nr", property)
## Private convenience function to get a setting from every extruder. ## Private convenience function to get a setting from every extruder.
# #
# For single extrusion machines, this gets the setting from the global # For single extrusion machines, this gets the setting from the global
@ -899,44 +890,6 @@ class BuildVolume(SceneNode):
all_values[i] = 0 all_values[i] = 0
return all_values return all_values
## Private convenience function to get a setting from the support infill
# extruder.
#
# \param setting_key The key of the setting to get.
# \param property The property to get from the setting.
# \return The property of the specified setting in the support infill
# extruder.
def _getSettingFromSupportInfillExtruder(self, setting_key, property = "value"):
return self._getSettingFromExtruder(setting_key, "support_infill_extruder_nr", property)
## Helper function to get a setting from an extruder specified in another
# setting.
#
# \param setting_key The key of the setting to get.
# \param extruder_setting_key The key of the setting that specifies from
# which extruder to get the setting, if there are multiple extruders.
# \param property The property to get from the setting.
# \return The property of the specified setting in the specified extruder.
def _getSettingFromExtruder(self, setting_key, extruder_setting_key, property = "value"):
multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1
if not multi_extrusion:
stack = self._global_container_stack
else:
extruder_index = self._global_container_stack.getProperty(extruder_setting_key, "value")
if str(extruder_index) == "-1": # If extruder index is -1 use global instead
stack = self._global_container_stack
else:
extruder_stack_id = ExtruderManager.getInstance().extruderIds[str(extruder_index)]
stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
value = stack.getProperty(setting_key, property)
setting_type = stack.getProperty(setting_key, "type")
if not value and (setting_type == "int" or setting_type == "float"):
return 0
return value
## Convenience function to calculate the disallowed radius around the edge. ## Convenience function to calculate the disallowed radius around the edge.
# #
# This disallowed radius is to allow for space around the models that is # This disallowed radius is to allow for space around the models that is
@ -945,6 +898,7 @@ class BuildVolume(SceneNode):
def _getEdgeDisallowedSize(self): def _getEdgeDisallowedSize(self):
if not self._global_container_stack: if not self._global_container_stack:
return 0 return 0
container_stack = self._global_container_stack container_stack = self._global_container_stack
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks() used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
@ -953,32 +907,44 @@ class BuildVolume(SceneNode):
return 0.1 # Return a very small value, so we do draw disallowed area's near the edges. return 0.1 # Return a very small value, so we do draw disallowed area's near the edges.
adhesion_type = container_stack.getProperty("adhesion_type", "value") adhesion_type = container_stack.getProperty("adhesion_type", "value")
skirt_brim_line_width = self._global_container_stack.getProperty("skirt_brim_line_width", "value")
initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value")
if adhesion_type == "skirt": if adhesion_type == "skirt":
skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap") skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value")
skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count") skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value")
bed_adhesion_size = skirt_distance + (self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * skirt_line_count) * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0
if len(used_extruders) > 1: bed_adhesion_size = skirt_distance + (skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0
for extruder_stack in used_extruders: for extruder_stack in used_extruders:
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
# We don't create an additional line for the extruder we're printing the skirt with. # We don't create an additional line for the extruder we're printing the skirt with.
bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
elif adhesion_type == "brim": elif adhesion_type == "brim":
bed_adhesion_size = self._getSettingFromAdhesionExtruder("skirt_brim_line_width") * self._getSettingFromAdhesionExtruder("brim_line_count") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor") / 100.0 brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
for extruder_stack in used_extruders: for extruder_stack in used_extruders:
bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0 bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
# We don't create an additional line for the extruder we're printing the brim with. # We don't create an additional line for the extruder we're printing the brim with.
bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0 bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
elif adhesion_type == "raft": elif adhesion_type == "raft":
bed_adhesion_size = self._getSettingFromAdhesionExtruder("raft_margin") bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value")
elif adhesion_type == "none": elif adhesion_type == "none":
bed_adhesion_size = 0 bed_adhesion_size = 0
else: else:
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?") raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
support_expansion = 0 support_expansion = 0
if self._getSettingFromSupportInfillExtruder("support_offset") and self._global_container_stack.getProperty("support_enable", "value"): support_enabled = self._global_container_stack.getProperty("support_enable", "value")
support_expansion += self._getSettingFromSupportInfillExtruder("support_offset") support_offset = self._global_container_stack.getProperty("support_offset", "value")
if support_enabled and support_offset:
support_expansion += support_offset
farthest_shield_distance = 0 farthest_shield_distance = 0
if container_stack.getProperty("draft_shield_enabled", "value"): if container_stack.getProperty("draft_shield_enabled", "value"):

View file

@ -12,8 +12,8 @@ class CameraAnimation(QVariantAnimation):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._camera_tool = None self._camera_tool = None
self.setDuration(500) self.setDuration(300)
self.setEasingCurve(QEasingCurve.InOutQuad) self.setEasingCurve(QEasingCurve.OutQuad)
def setCameraTool(self, camera_tool): def setCameraTool(self, camera_tool):
self._camera_tool = camera_tool self._camera_tool = camera_tool

View file

@ -302,24 +302,23 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._onChanged() self._onChanged()
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property). ## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
def _getSettingProperty(self, setting_key, property = "value"): def _getSettingProperty(self, setting_key, prop = "value"):
per_mesh_stack = self._node.callDecoration("getStack") per_mesh_stack = self._node.callDecoration("getStack")
if per_mesh_stack: if per_mesh_stack:
return per_mesh_stack.getProperty(setting_key, property) return per_mesh_stack.getProperty(setting_key, prop)
multi_extrusion = self._global_stack.getProperty("machine_extruder_count", "value") > 1
if not multi_extrusion:
return self._global_stack.getProperty(setting_key, property)
extruder_index = self._global_stack.getProperty(setting_key, "limit_to_extruder") extruder_index = self._global_stack.getProperty(setting_key, "limit_to_extruder")
if extruder_index == "-1": #No limit_to_extruder. if extruder_index == "-1":
# No limit_to_extruder
extruder_stack_id = self._node.callDecoration("getActiveExtruder") extruder_stack_id = self._node.callDecoration("getActiveExtruder")
if not extruder_stack_id: #Decoration doesn't exist. if not extruder_stack_id:
# Decoration doesn't exist
extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"] extruder_stack_id = ExtruderManager.getInstance().extruderIds["0"]
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
return extruder_stack.getProperty(setting_key, property) return extruder_stack.getProperty(setting_key, prop)
else: #Limit_to_extruder is set. The global stack handles this then. else:
return self._global_stack.getProperty(setting_key, property) # Limit_to_extruder is set. The global stack handles this then
return self._global_stack.getProperty(setting_key, prop)
## Returns true if node is a descendant or the same as the root node. ## Returns true if node is a descendant or the same as the root node.
def __isDescendant(self, root, node): def __isDescendant(self, root, node):

View file

@ -53,6 +53,7 @@ class CrashHandler:
self.exception_type = exception_type self.exception_type = exception_type
self.value = value self.value = value
self.traceback = tb self.traceback = tb
self.dialog = QDialog()
# While we create the GUI, the information will be stored for sending afterwards # While we create the GUI, the information will be stored for sending afterwards
self.data = dict() self.data = dict()
@ -74,7 +75,6 @@ class CrashHandler:
## Creates a modal dialog. ## Creates a modal dialog.
def _createDialog(self): def _createDialog(self):
self.dialog = QDialog()
self.dialog.setMinimumWidth(640) self.dialog.setMinimumWidth(640)
self.dialog.setMinimumHeight(640) self.dialog.setMinimumHeight(640)
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report")) self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
@ -108,11 +108,11 @@ class CrashHandler:
except: except:
self.cura_version = catalog.i18nc("@label unknown version of Cura", "Unknown") self.cura_version = catalog.i18nc("@label unknown version of Cura", "Unknown")
crash_info = catalog.i18nc("@label Cura version", "<b>Cura version:</b> {version}<br/>").format(version = self.cura_version) crash_info = "<b>" + catalog.i18nc("@label Cura version number", "Cura version") + ":</b> " + str(self.cura_version) + "<br/>"
crash_info += catalog.i18nc("@label Platform", "<b>Platform:</b> {platform}<br/>").format(platform = platform.platform()) crash_info += "<b>" + catalog.i18nc("@label Type of platform", "Platform") + ":</b> " + str(platform.platform()) + "<br/>"
crash_info += catalog.i18nc("@label Qt version", "<b>Qt version:</b> {qt}<br/>").format(qt = QT_VERSION_STR) crash_info += "<b>" + catalog.i18nc("@label", "Qt version") + ":</b> " + str(QT_VERSION_STR) + "<br/>"
crash_info += catalog.i18nc("@label PyQt version", "<b>PyQt version:</b> {pyqt}<br/>").format(pyqt = PYQT_VERSION_STR) crash_info += "<b>" + catalog.i18nc("@label", "PyQt version") + ":</b> " + str(PYQT_VERSION_STR) + "<br/>"
crash_info += catalog.i18nc("@label OpenGL", "<b>OpenGL:</b> {opengl}<br/>").format(opengl = self._getOpenGLInfo()) crash_info += "<b>" + catalog.i18nc("@label OpenGL version", "OpenGL") + ":</b> " + str(self._getOpenGLInfo()) + "<br/>"
label.setText(crash_info) label.setText(crash_info)
layout.addWidget(label) layout.addWidget(label)
@ -126,13 +126,18 @@ class CrashHandler:
return group return group
def _getOpenGLInfo(self): def _getOpenGLInfo(self):
opengl_instance = OpenGL.getInstance()
if not opengl_instance:
self.data["opengl"] = {"version": "n/a", "vendor": "n/a", "type": "n/a"}
return catalog.i18nc("@label", "not yet initialised<br/>")
info = "<ul>" info = "<ul>"
info += catalog.i18nc("@label OpenGL version", "<li>OpenGL Version: {version}</li>").format(version = OpenGL.getInstance().getOpenGLVersion()) info += catalog.i18nc("@label OpenGL version", "<li>OpenGL Version: {version}</li>").format(version = opengl_instance.getOpenGLVersion())
info += catalog.i18nc("@label OpenGL vendor", "<li>OpenGL Vendor: {vendor}</li>").format(vendor = OpenGL.getInstance().getGPUVendorName()) info += catalog.i18nc("@label OpenGL vendor", "<li>OpenGL Vendor: {vendor}</li>").format(vendor = opengl_instance.getGPUVendorName())
info += catalog.i18nc("@label OpenGL renderer", "<li>OpenGL Renderer: {renderer}</li>").format(renderer = OpenGL.getInstance().getGPUType()) info += catalog.i18nc("@label OpenGL renderer", "<li>OpenGL Renderer: {renderer}</li>").format(renderer = opengl_instance.getGPUType())
info += "</ul>" info += "</ul>"
self.data["opengl"] = {"version": OpenGL.getInstance().getOpenGLVersion(), "vendor": OpenGL.getInstance().getGPUVendorName(), "type": OpenGL.getInstance().getGPUType()} self.data["opengl"] = {"version": opengl_instance.getOpenGLVersion(), "vendor": opengl_instance.getGPUVendorName(), "type": opengl_instance.getGPUType()}
return info return info
@ -280,5 +285,7 @@ class CrashHandler:
Application.getInstance().callLater(self._show) Application.getInstance().callLater(self._show)
def _show(self): def _show(self):
# When the exception is not in the fatal_exception_types list, the dialog is not created, so we don't need to show it
if self.dialog:
self.dialog.exec_() self.dialog.exec_()
os._exit(1) os._exit(1)

View file

@ -105,7 +105,7 @@ class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions. # SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible # You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings. # changes of the settings.
SettingVersion = 3 SettingVersion = 4
class ResourceTypes: class ResourceTypes:
QmlFiles = Resources.UserType + 1 QmlFiles = Resources.UserType + 1
@ -126,8 +126,6 @@ class CuraApplication(QtApplication):
# Cura will always show the Add Machine Dialog upon start. # Cura will always show the Add Machine Dialog upon start.
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
projectFileLoaded = pyqtSignal(str) # Emitted whenever a project file is loaded
def __init__(self): def __init__(self):
# this list of dir names will be used by UM to detect an old cura directory # this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]: for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
@ -200,6 +198,7 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager() self._machine_action_manager = MachineActionManager.MachineActionManager()
self._machine_manager = None # This is initialized on demand. self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None
self._material_manager = None self._material_manager = None
self._setting_inheritance_manager = None self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None self._simple_mode_settings_manager = None
@ -215,8 +214,9 @@ class CuraApplication(QtApplication):
self.setRequiredPlugins([ self.setRequiredPlugins([
"CuraEngineBackend", "CuraEngineBackend",
"UserAgreement",
"SolidView", "SolidView",
"LayerView", "SimulationView",
"STLReader", "STLReader",
"SelectionTool", "SelectionTool",
"CameraTool", "CameraTool",
@ -259,20 +259,25 @@ class CuraApplication(QtApplication):
# Since they are empty, they should never be serialized and instead just programmatically created. # Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials. # We need them to simplify the switching between materials.
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
empty_variant_container = copy.deepcopy(empty_container) empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container.setMetaDataEntry("id", "empty_variant") empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant") empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container) ContainerRegistry.getInstance().addContainer(empty_variant_container)
empty_material_container = copy.deepcopy(empty_container) empty_material_container = copy.deepcopy(empty_container)
empty_material_container.setMetaDataEntry("id", "empty_material") empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material") empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container) ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container) empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", "empty_quality") empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported") empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "normal") empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality") empty_quality_container.addMetaDataEntry("type", "quality")
empty_quality_container.addMetaDataEntry("supported", False)
ContainerRegistry.getInstance().addContainer(empty_quality_container) ContainerRegistry.getInstance().addContainer(empty_quality_container)
empty_quality_changes_container = copy.deepcopy(empty_container) empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
@ -303,6 +308,8 @@ class CuraApplication(QtApplication):
preferences.addPreference("view/invert_zoom", False) preferences.addPreference("view/invert_zoom", False)
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
for key in [ for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path", "dialog_profile_path",
@ -375,6 +382,14 @@ class CuraApplication(QtApplication):
def _onEngineCreated(self): def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@pyqtProperty(bool)
def needToShowUserAgreement(self):
return self._need_to_show_user_agreement
def setNeedToShowUserAgreement(self, set_value = True):
self._need_to_show_user_agreement = set_value
## The "Quit" button click event handler. ## The "Quit" button click event handler.
@pyqtSlot() @pyqtSlot()
def closeApplication(self): def closeApplication(self):
@ -393,6 +408,7 @@ class CuraApplication(QtApplication):
showDiscardOrKeepProfileChanges = pyqtSignal() showDiscardOrKeepProfileChanges = pyqtSignal()
def discardOrKeepProfileChanges(self): def discardOrKeepProfileChanges(self):
has_user_interaction = False
choice = Preferences.getInstance().getValue("cura/choice_on_profile_override") choice = Preferences.getInstance().getValue("cura/choice_on_profile_override")
if choice == "always_discard": if choice == "always_discard":
# don't show dialog and DISCARD the profile # don't show dialog and DISCARD the profile
@ -403,18 +419,36 @@ class CuraApplication(QtApplication):
else: else:
# ALWAYS ask whether to keep or discard the profile # ALWAYS ask whether to keep or discard the profile
self.showDiscardOrKeepProfileChanges.emit() self.showDiscardOrKeepProfileChanges.emit()
has_user_interaction = True
return has_user_interaction
#sidebarSimpleDiscardOrKeepProfileChanges = pyqtSignal() onDiscardOrKeepProfileChangesClosed = pyqtSignal() # Used to notify other managers that the dialog was closed
@pyqtSlot(str) @pyqtSlot(str)
def discardOrKeepProfileChangesClosed(self, option): def discardOrKeepProfileChangesClosed(self, option):
if option == "discard": if option == "discard":
global_stack = self.getGlobalContainerStack() global_stack = self.getGlobalContainerStack()
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
extruder.getTop().clear() extruder.getTop().clear()
global_stack.getTop().clear() global_stack.getTop().clear()
# if the user decided to keep settings then the user settings should be re-calculated and validated for errors
# before slicing. To ensure that slicer uses right settings values
elif option == "keep":
global_stack = self.getGlobalContainerStack()
for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
user_extruder_container = extruder.getTop()
if user_extruder_container:
user_extruder_container.update()
user_global_container = global_stack.getTop()
if user_global_container:
user_global_container.update()
# notify listeners that quality has changed (after user selected discard or keep)
self.onDiscardOrKeepProfileChangesClosed.emit()
self.getMachineManager().activeQualityChanged.emit()
@pyqtSlot(int) @pyqtSlot(int)
def messageBoxClosed(self, button): def messageBoxClosed(self, button):
if self._message_box_callback: if self._message_box_callback:
@ -528,6 +562,7 @@ class CuraApplication(QtApplication):
super().addCommandLineOptions(parser) super().addCommandLineOptions(parser)
parser.add_argument("file", nargs="*", help="Files to load after starting the application.") parser.add_argument("file", nargs="*", help="Files to load after starting the application.")
parser.add_argument("--single-instance", action="store_true", default=False) parser.add_argument("--single-instance", action="store_true", default=False)
parser.add_argument("--headless", action = "store_true", default=False)
# Set up a local socket server which listener which coordinates single instances Curas and accepts commands. # Set up a local socket server which listener which coordinates single instances Curas and accepts commands.
def _setUpSingleInstanceServer(self): def _setUpSingleInstanceServer(self):
@ -664,25 +699,25 @@ class CuraApplication(QtApplication):
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface...")) self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))
# Initialise extruder so as to listen to global container stack changes before the first global container stack is set. qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
ExtruderManager.getInstance()
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager) qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager) qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
self.getSettingInheritanceManager) qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml")) self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles)) self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
run_headless = self.getCommandLineOption("headless", False)
if not run_headless:
self.initializeEngine() self.initializeEngine()
if self._engine.rootObjects: if run_headless or self._engine.rootObjects:
self.closeSplash() self.closeSplash()
for file in self.getCommandLineOption("file", []): for file_name in self.getCommandLineOption("file", []):
self._openFile(file) self._openFile(file_name)
for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading. for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading.
self._openFile(file_name) self._openFile(file_name)
@ -695,6 +730,11 @@ class CuraApplication(QtApplication):
self._machine_manager = MachineManager.createMachineManager() self._machine_manager = MachineManager.createMachineManager()
return self._machine_manager return self._machine_manager
def getExtruderManager(self, *args):
if self._extruder_manager is None:
self._extruder_manager = ExtruderManager.createExtruderManager()
return self._extruder_manager
def getMaterialManager(self, *args): def getMaterialManager(self, *args):
if self._material_manager is None: if self._material_manager is None:
self._material_manager = MaterialManager.createMaterialManager() self._material_manager = MaterialManager.createMaterialManager()
@ -745,7 +785,6 @@ class CuraApplication(QtApplication):
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel) qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel") qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
@ -755,15 +794,12 @@ class CuraApplication(QtApplication):
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel") qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions") qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions")
engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.getInstance())
for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles): for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles):
type_name = os.path.splitext(os.path.basename(path))[0] type_name = os.path.splitext(os.path.basename(path))[0]
if type_name in ("Cura", "Actions"): if type_name in ("Cura", "Actions"):
@ -1229,6 +1265,9 @@ class CuraApplication(QtApplication):
# see GroupDecorator._onChildrenChanged # see GroupDecorator._onChildrenChanged
def _createSplashScreen(self): def _createSplashScreen(self):
run_headless = self.getCommandLineOption("headless", False)
if run_headless:
return None
return CuraSplashScreen.CuraSplashScreen() return CuraSplashScreen.CuraSplashScreen()
def _onActiveMachineChanged(self): def _onActiveMachineChanged(self):
@ -1342,7 +1381,7 @@ class CuraApplication(QtApplication):
extension = os.path.splitext(filename)[1] extension = os.path.splitext(filename)[1]
if extension.lower() in self._non_sliceable_extensions: if extension.lower() in self._non_sliceable_extensions:
self.getController().setActiveView("LayerView") self.getController().setActiveView("SimulationView")
view = self.getController().getActiveView() view = self.getController().getActiveView()
view.resetLayerData() view.resetLayerData()
view.setLayer(9999999) view.setLayer(9999999)

View file

@ -47,12 +47,12 @@ class Layer:
return result return result
def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices):
result_vertex_offset = vertex_offset result_vertex_offset = vertex_offset
result_index_offset = index_offset result_index_offset = index_offset
self._element_count = 0 self._element_count = 0
for polygon in self._polygons: for polygon in self._polygons:
polygon.build(result_vertex_offset, result_index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) polygon.build(result_vertex_offset, result_index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
result_vertex_offset += polygon.lineMeshVertexCount() result_vertex_offset += polygon.lineMeshVertexCount()
result_index_offset += polygon.lineMeshElementCount() result_index_offset += polygon.lineMeshElementCount()
self._element_count += polygon.elementCount self._element_count += polygon.elementCount

View file

@ -20,11 +20,11 @@ class LayerDataBuilder(MeshBuilder):
if layer not in self._layers: if layer not in self._layers:
self._layers[layer] = Layer(layer) self._layers[layer] = Layer(layer)
def addPolygon(self, layer, polygon_type, data, line_width): def addPolygon(self, layer, polygon_type, data, line_width, line_thickness, line_feedrate):
if layer not in self._layers: if layer not in self._layers:
self.addLayer(layer) self.addLayer(layer)
p = LayerPolygon(self, polygon_type, data, line_width) p = LayerPolygon(self, polygon_type, data, line_width, line_thickness, line_feedrate)
self._layers[layer].polygons.append(p) self._layers[layer].polygons.append(p)
def getLayer(self, layer): def getLayer(self, layer):
@ -64,13 +64,14 @@ class LayerDataBuilder(MeshBuilder):
line_dimensions = numpy.empty((vertex_count, 2), numpy.float32) line_dimensions = numpy.empty((vertex_count, 2), numpy.float32)
colors = numpy.empty((vertex_count, 4), numpy.float32) colors = numpy.empty((vertex_count, 4), numpy.float32)
indices = numpy.empty((index_count, 2), numpy.int32) indices = numpy.empty((index_count, 2), numpy.int32)
feedrates = numpy.empty((vertex_count), numpy.float32)
extruders = numpy.empty((vertex_count), numpy.float32) extruders = numpy.empty((vertex_count), numpy.float32)
line_types = numpy.empty((vertex_count), numpy.float32) line_types = numpy.empty((vertex_count), numpy.float32)
vertex_offset = 0 vertex_offset = 0
index_offset = 0 index_offset = 0
for layer, data in sorted(self._layers.items()): for layer, data in sorted(self._layers.items()):
( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices) ( vertex_offset, index_offset ) = data.build( vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices)
self._element_counts[layer] = data.elementCount self._element_counts[layer] = data.elementCount
self.addVertices(vertices) self.addVertices(vertices)
@ -107,6 +108,11 @@ class LayerDataBuilder(MeshBuilder):
"value": line_types, "value": line_types,
"opengl_name": "a_line_type", "opengl_name": "a_line_type",
"opengl_type": "float" "opengl_type": "float"
},
"feedrates": {
"value": feedrates,
"opengl_name": "a_feedrate",
"opengl_type": "float"
} }
} }

View file

@ -28,7 +28,8 @@ class LayerPolygon:
# \param data new_points # \param data new_points
# \param line_widths array with line widths # \param line_widths array with line widths
# \param line_thicknesses: array with type as index and thickness as value # \param line_thicknesses: array with type as index and thickness as value
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses): # \param line_feedrates array with line feedrates
def __init__(self, extruder, line_types, data, line_widths, line_thicknesses, line_feedrates):
self._extruder = extruder self._extruder = extruder
self._types = line_types self._types = line_types
for i in range(len(self._types)): for i in range(len(self._types)):
@ -37,6 +38,7 @@ class LayerPolygon:
self._data = data self._data = data
self._line_widths = line_widths self._line_widths = line_widths
self._line_thicknesses = line_thicknesses self._line_thicknesses = line_thicknesses
self._line_feedrates = line_feedrates
self._vertex_begin = 0 self._vertex_begin = 0
self._vertex_end = 0 self._vertex_end = 0
@ -84,10 +86,11 @@ class LayerPolygon:
# \param vertices : vertex numpy array to be filled # \param vertices : vertex numpy array to be filled
# \param colors : vertex numpy array to be filled # \param colors : vertex numpy array to be filled
# \param line_dimensions : vertex numpy array to be filled # \param line_dimensions : vertex numpy array to be filled
# \param feedrates : vertex numpy array to be filled
# \param extruders : vertex numpy array to be filled # \param extruders : vertex numpy array to be filled
# \param line_types : vertex numpy array to be filled # \param line_types : vertex numpy array to be filled
# \param indices : index numpy array to be filled # \param indices : index numpy array to be filled
def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, extruders, line_types, indices): def build(self, vertex_offset, index_offset, vertices, colors, line_dimensions, feedrates, extruders, line_types, indices):
if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None: if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
self.buildCache() self.buildCache()
@ -109,10 +112,13 @@ class LayerPolygon:
# Create an array with colors for each vertex and remove the color data for the points that has been thrown away. # Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()] colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
# Create an array with line widths for each vertex. # Create an array with line widths and thicknesses for each vertex.
line_dimensions[self._vertex_begin:self._vertex_end, 0] = numpy.tile(self._line_widths, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] line_dimensions[self._vertex_begin:self._vertex_end, 0] = numpy.tile(self._line_widths, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
line_dimensions[self._vertex_begin:self._vertex_end, 1] = numpy.tile(self._line_thicknesses, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0] line_dimensions[self._vertex_begin:self._vertex_end, 1] = numpy.tile(self._line_thicknesses, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
# Create an array with feedrates for each line
feedrates[self._vertex_begin:self._vertex_end] = numpy.tile(self._line_feedrates, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
extruders[self._vertex_begin:self._vertex_end] = self._extruder extruders[self._vertex_begin:self._vertex_end] = self._extruder
# Convert type per vertex to type per line # Convert type per vertex to type per line
@ -167,6 +173,14 @@ class LayerPolygon:
def lineWidths(self): def lineWidths(self):
return self._line_widths return self._line_widths
@property
def lineThicknesses(self):
return self._line_thicknesses
@property
def lineFeedrates(self):
return self._line_feedrates
@property @property
def jumpMask(self): def jumpMask(self):
return self._jump_mask return self._jump_mask

View file

@ -73,14 +73,8 @@ class MachineAction(QObject, PluginObject):
## Protected helper to create a view object based on provided QML. ## Protected helper to create a view object based on provided QML.
def _createViewFromQML(self): def _createViewFromQML(self):
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)) path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)
self._component = QQmlComponent(Application.getInstance()._engine, path) self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
self._context.setContextProperty("manager", self)
self._view = self._component.create(self._context)
if self._view is None:
Logger.log("c", "QQmlComponent status %s", self._component.status())
Logger.log("c", "QQmlComponent error string %s", self._component.errorString())
@pyqtProperty(QObject, constant = True) @pyqtProperty(QObject, constant = True)
def displayItem(self): def displayItem(self):

57
cura/PreviewPass.py Normal file
View file

@ -0,0 +1,57 @@
# Copyright (c) 2017 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Resources import Resources
from UM.View.RenderPass import RenderPass
from UM.View.GL.OpenGL import OpenGL
from UM.View.RenderBatch import RenderBatch
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from typing import Optional
MYPY = False
if MYPY:
from UM.Scene.Camera import Camera
## A render pass subclass that renders slicable objects with default parameters.
# It uses the active camera by default, but it can be overridden to use a different camera.
#
# This is useful to get a preview image of a scene taken from a different location as the active camera.
class PreviewPass(RenderPass):
def __init__(self, width: int, height: int):
super().__init__("preview", width, height, 0)
self._camera = None # type: Optional[Camera]
self._renderer = Application.getInstance().getRenderer()
self._shader = None
self._scene = Application.getInstance().getController().getScene()
# Set the camera to be used by this render pass
# if it's None, the active camera is used
def setCamera(self, camera: Optional["Camera"]):
self._camera = camera
def render(self) -> None:
if not self._shader:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "object.shader"))
# Create a new batch to be rendered
batch = RenderBatch(self._shader)
# Fill up the batch with objects that can be sliced. `
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
batch.addItem(node.getWorldTransformation(), node.getMeshData())
self.bind()
if self._camera is None:
batch.render(Application.getInstance().getController().getScene().getActiveCamera())
else:
batch.render(self._camera)
self.release()

View file

@ -56,6 +56,7 @@ class PrintInformation(QObject):
self._material_lengths = [] self._material_lengths = []
self._material_weights = [] self._material_weights = []
self._material_costs = [] self._material_costs = []
self._material_names = []
self._pre_sliced = False self._pre_sliced = False
@ -66,11 +67,10 @@ class PrintInformation(QObject):
self._base_name = "" self._base_name = ""
self._abbr_machine = "" self._abbr_machine = ""
self._job_name = "" self._job_name = ""
self._project_name = ""
Application.getInstance().globalContainerStackChanged.connect(self._updateJobName) Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
Application.getInstance().fileLoaded.connect(self.setBaseName) Application.getInstance().fileLoaded.connect(self.setBaseName)
Application.getInstance().projectFileLoaded.connect(self.setProjectName) Application.getInstance().workspaceLoaded.connect(self.setProjectName)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._active_material_container = None self._active_material_container = None
@ -139,6 +139,12 @@ class PrintInformation(QObject):
def materialCosts(self): def materialCosts(self):
return self._material_costs return self._material_costs
materialNamesChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialNamesChanged)
def materialNames(self):
return self._material_names
def _onPrintDurationMessage(self, print_time, material_amounts): def _onPrintDurationMessage(self, print_time, material_amounts):
self._updateTotalPrintTimePerFeature(print_time) self._updateTotalPrintTimePerFeature(print_time)
@ -170,6 +176,7 @@ class PrintInformation(QObject):
self._material_lengths = [] self._material_lengths = []
self._material_weights = [] self._material_weights = []
self._material_costs = [] self._material_costs = []
self._material_names = []
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings")) material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
@ -188,8 +195,10 @@ class PrintInformation(QObject):
weight = float(amount) * float(density) / 1000 weight = float(amount) * float(density) / 1000
cost = 0 cost = 0
material_name = catalog.i18nc("@label unknown material", "Unknown")
if material: if material:
material_guid = material.getMetaDataEntry("GUID") material_guid = material.getMetaDataEntry("GUID")
material_name = material.getName()
if material_guid in material_preference_values: if material_guid in material_preference_values:
material_values = material_preference_values[material_guid] material_values = material_preference_values[material_guid]
@ -208,10 +217,12 @@ class PrintInformation(QObject):
self._material_weights.append(weight) self._material_weights.append(weight)
self._material_lengths.append(length) self._material_lengths.append(length)
self._material_costs.append(cost) self._material_costs.append(cost)
self._material_names.append(material_name)
self.materialLengthsChanged.emit() self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit() self.materialWeightsChanged.emit()
self.materialCostsChanged.emit() self.materialCostsChanged.emit()
self.materialNamesChanged.emit()
def _onPreferencesChanged(self, preference): def _onPreferencesChanged(self, preference):
if preference != "cura/material_settings": if preference != "cura/material_settings":
@ -241,11 +252,6 @@ class PrintInformation(QObject):
self._job_name = name self._job_name = name
self.jobNameChanged.emit() self.jobNameChanged.emit()
@pyqtSlot(str)
def setProjectName(self, name):
self._project_name = name
self.setJobName(name)
jobNameChanged = pyqtSignal() jobNameChanged = pyqtSignal()
@pyqtProperty(str, notify = jobNameChanged) @pyqtProperty(str, notify = jobNameChanged)
@ -253,11 +259,6 @@ class PrintInformation(QObject):
return self._job_name return self._job_name
def _updateJobName(self): def _updateJobName(self):
# if the project name is set, we use the project name as the job name, so the job name should not get updated
# if a model file is loaded after that.
if self._project_name != "":
return
if self._base_name == "": if self._base_name == "":
self._job_name = "" self._job_name = ""
self.jobNameChanged.emit() self.jobNameChanged.emit()
@ -283,7 +284,11 @@ class PrintInformation(QObject):
return self._base_name return self._base_name
@pyqtSlot(str) @pyqtSlot(str)
def setBaseName(self, base_name): def setProjectName(self, name):
self.setBaseName(name, is_project_file = True)
@pyqtSlot(str)
def setBaseName(self, base_name, is_project_file = False):
# Ensure that we don't use entire path but only filename # Ensure that we don't use entire path but only filename
name = os.path.basename(base_name) name = os.path.basename(base_name)
@ -291,11 +296,17 @@ class PrintInformation(QObject):
# extension. This cuts the extension off if necessary. # extension. This cuts the extension off if necessary.
name = os.path.splitext(name)[0] name = os.path.splitext(name)[0]
# if this is a profile file, always update the job name
# name is "" when I first had some meshes and afterwards I deleted them so the naming should start again # name is "" when I first had some meshes and afterwards I deleted them so the naming should start again
if name == "" or (self._base_name == "" and self._base_name != name): is_empty = name == ""
if is_project_file or (is_empty or (self._base_name == "" and self._base_name != name)):
# remove ".curaproject" suffix from (imported) the file name
if name.endswith(".curaproject"):
name = name[:name.rfind(".curaproject")]
self._base_name = name self._base_name = name
self._updateJobName() self._updateJobName()
## Created an acronymn-like abbreviated machine name from the currently active machine name ## Created an acronymn-like abbreviated machine name from the currently active machine name
# Called each time the global stack is switched # Called each time the global stack is switched
def _setAbbreviatedMachineName(self): def _setAbbreviatedMachineName(self):
@ -313,7 +324,12 @@ class PrintInformation(QObject):
elif word.isdigit(): elif word.isdigit():
abbr_machine += word abbr_machine += word
else: else:
abbr_machine += self._stripAccents(word.strip("()[]{}#").upper())[0] stripped_word = self._stripAccents(word.strip("()[]{}#").upper())
# - use only the first character if the word is too long (> 3 characters)
# - use the whole word if it's not too long (<= 3 characters)
if len(stripped_word) > 3:
stripped_word = stripped_word[0]
abbr_machine += stripped_word
self._abbr_machine = abbr_machine self._abbr_machine = abbr_machine
@ -339,4 +355,3 @@ class PrintInformation(QObject):
temp_material_amounts = [0] temp_material_amounts = [0]
self._onPrintDurationMessage(temp_message, temp_material_amounts) self._onPrintDurationMessage(temp_message, temp_material_amounts)

View file

@ -74,6 +74,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._can_pause = True self._can_pause = True
self._can_abort = True self._can_abort = True
self._can_pre_heat_bed = True self._can_pre_heat_bed = True
self._can_control_manually = True
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None): def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
raise NotImplementedError("requestWrite needs to be implemented") raise NotImplementedError("requestWrite needs to be implemented")
@ -144,6 +145,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
def canAbort(self): def canAbort(self):
return self._can_abort return self._can_abort
# Does the printer support manual control at all
@pyqtProperty(bool, constant=True)
def canControlManually(self):
return self._can_control_manually
@pyqtProperty(QObject, constant=True) @pyqtProperty(QObject, constant=True)
def monitorItem(self): def monitorItem(self):
# Note that we specifically only check if the monitor component is created. # Note that we specifically only check if the monitor component is created.

View file

@ -86,7 +86,7 @@ class QualityManager:
qualities = set(quality_type_dict.values()) qualities = set(quality_type_dict.values())
for material_container in material_containers[1:]: for material_container in material_containers[1:]:
next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container) next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
qualities.update(set(next_quality_type_dict.values())) qualities.intersection_update(set(next_quality_type_dict.values()))
return list(qualities) return list(qualities)
@ -177,12 +177,16 @@ class QualityManager:
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]: def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
global_machine_definition = global_container_stack.getBottom() global_machine_definition = global_container_stack.getBottom()
if extruder_stacks: machine_manager = Application.getInstance().getMachineManager()
# Multi-extruder machine detected. active_stack_id = machine_manager.activeStackId
materials = [stack.material for stack in extruder_stacks]
materials = []
for stack in extruder_stacks:
if stack.getId() == active_stack_id and machine_manager.newMaterial:
materials.append(machine_manager.newMaterial)
else: else:
# Machine with one extruder. materials.append(stack.material)
materials = [global_container_stack.material]
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials) quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)

View file

@ -4,6 +4,7 @@
import os import os
import os.path import os.path
import re import re
import configparser
from typing import Optional from typing import Optional
@ -19,6 +20,7 @@ from UM.Message import Message
from UM.Platform import Platform from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry # For getting the possible profile writers to write with. from UM.PluginRegistry import PluginRegistry # For getting the possible profile writers to write with.
from UM.Util import parseBool from UM.Util import parseBool
from UM.Resources import Resources
from . import ExtruderStack from . import ExtruderStack
from . import GlobalStack from . import GlobalStack
@ -256,7 +258,8 @@ class CuraContainerRegistry(ContainerRegistry):
@override(ContainerRegistry) @override(ContainerRegistry)
def load(self): def load(self):
super().load() super().load()
self._fixupExtruders() self._registerSingleExtrusionMachinesExtruderStacks()
self._connectUpgradedExtruderStacksToMachines()
## Update an imported profile to match the current machine configuration. ## Update an imported profile to match the current machine configuration.
# #
@ -298,11 +301,17 @@ class CuraContainerRegistry(ContainerRegistry):
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
del quality_type_criteria["definition"] del quality_type_criteria["definition"]
materials = None
# materials = None
if "material" in quality_type_criteria: if "material" in quality_type_criteria:
materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"]) # materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
del quality_type_criteria["material"] del quality_type_criteria["material"]
# Do not filter quality containers here with materials because we are trying to import a profile, so it should
# NOT be restricted by the active materials on the current machine.
materials = None
# Check to make sure the imported profile actually makes sense in context of the current configuration. # Check to make sure the imported profile actually makes sense in context of the current configuration.
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as # This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
# successfully imported but then fail to show up. # successfully imported but then fail to show up.
@ -356,8 +365,8 @@ class CuraContainerRegistry(ContainerRegistry):
return global_container_stack.material.getId() return global_container_stack.material.getId()
return "" return ""
## Returns true if the current machien requires its own quality profiles ## Returns true if the current machine requires its own quality profiles
# \return true if the current machien requires its own quality profiles # \return true if the current machine requires its own quality profiles
def _machineHasOwnQualities(self): def _machineHasOwnQualities(self):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
@ -390,12 +399,135 @@ class CuraContainerRegistry(ContainerRegistry):
return new_stack return new_stack
def _registerSingleExtrusionMachinesExtruderStacks(self):
machines = self.findContainerStacks(type = "machine", machine_extruder_trains = {"0": "fdmextruder"})
for machine in machines:
extruder_stacks = self.findContainerStacks(type = "extruder_train", machine = machine.getId())
if not extruder_stacks:
self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder")
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id):
new_extruder_id = extruder_id
extruder_definitions = self.findDefinitionContainers(id = new_extruder_id)
if not extruder_definitions:
Logger.log("w", "Could not find definition containers for extruder %s", new_extruder_id)
return
extruder_definition = extruder_definitions[0]
unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id)
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.setNextStack(machine)
# create empty user changes container otherwise
user_container = InstanceContainer(extruder_stack.id + "_user")
user_container.addMetaDataEntry("type", "user")
user_container.addMetaDataEntry("machine", extruder_stack.getId())
from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(machine.definition)
if machine.userChanges:
# for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
# container to the extruder stack.
for user_setting_key in machine.userChanges.getAllKeys():
settable_per_extruder = machine.getProperty(user_setting_key, "settable_per_extruder")
if settable_per_extruder:
user_container.addInstance(machine.userChanges.getInstance(user_setting_key))
machine.userChanges.removeInstance(user_setting_key, postpone_emit = True)
extruder_stack.setUserChanges(user_container)
self.addContainer(user_container)
variant_id = "default"
if machine.variant.getId() not in ("empty", "empty_variant"):
variant_id = machine.variant.getId()
else:
variant_id = "empty_variant"
extruder_stack.setVariantById(variant_id)
material_id = "default"
if machine.material.getId() not in ("empty", "empty_material"):
material_id = machine.material.getId()
else:
material_id = "empty_material"
extruder_stack.setMaterialById(material_id)
quality_id = "default"
if machine.quality.getId() not in ("empty", "empty_quality"):
quality_id = machine.quality.getId()
else:
quality_id = "empty_quality"
extruder_stack.setQualityById(quality_id)
if machine.qualityChanges.getId() not in ("empty", "empty_quality_changes"):
extruder_quality_changes_container = self.findInstanceContainers(name = machine.qualityChanges.getName(), extruder = extruder_id)
if extruder_quality_changes_container:
extruder_quality_changes_container = extruder_quality_changes_container[0]
quality_changes_id = extruder_quality_changes_container.getId()
extruder_stack.setQualityChangesById(quality_changes_id)
else:
# Some extruder quality_changes containers can be created at runtime as files in the qualities
# folder. Those files won't be loaded in the registry immediately. So we also need to search
# the folder to see if the quality_changes exists.
extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine.qualityChanges.getName())
if extruder_quality_changes_container:
quality_changes_id = extruder_quality_changes_container.getId()
extruder_stack.setQualityChangesById(quality_changes_id)
if not extruder_quality_changes_container:
Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]",
machine.qualityChanges.getName(), extruder_stack.getId())
else:
extruder_stack.setQualityChangesById("empty_quality_changes")
self.addContainer(extruder_stack)
return extruder_stack
def _findQualityChangesContainerInCuraFolder(self, name):
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
instance_container = None
for item in os.listdir(quality_changes_dir):
file_path = os.path.join(quality_changes_dir, item)
if not os.path.isfile(file_path):
continue
parser = configparser.ConfigParser()
try:
parser.read([file_path])
except:
# skip, it is not a valid stack file
continue
if not parser.has_option("general", "name"):
continue
if parser["general"]["name"] == name:
# load the container
container_id = os.path.basename(file_path).replace(".inst.cfg", "")
instance_container = InstanceContainer(container_id)
with open(file_path, "r") as f:
serialized = f.read()
instance_container.deserialize(serialized, file_path)
self.addContainer(instance_container)
break
return instance_container
# Fix the extruders that were upgraded to ExtruderStack instances during addContainer. # Fix the extruders that were upgraded to ExtruderStack instances during addContainer.
# The stacks are now responsible for setting the next stack on deserialize. However, # The stacks are now responsible for setting the next stack on deserialize. However,
# due to problems with loading order, some stacks may not have the proper next stack # due to problems with loading order, some stacks may not have the proper next stack
# set after upgrading, because the proper global stack was not yet loaded. This method # set after upgrading, because the proper global stack was not yet loaded. This method
# makes sure those extruders also get the right stack set. # makes sure those extruders also get the right stack set.
def _fixupExtruders(self): def _connectUpgradedExtruderStacksToMachines(self):
extruder_stacks = self.findContainers(container_type = ExtruderStack.ExtruderStack) extruder_stacks = self.findContainers(container_type = ExtruderStack.ExtruderStack)
for extruder_stack in extruder_stacks: for extruder_stack in extruder_stacks:
if extruder_stack.getNextStack(): if extruder_stack.getNextStack():

View file

@ -41,9 +41,20 @@ class CuraContainerStack(ContainerStack):
def __init__(self, container_id: str, *args, **kwargs): def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs) super().__init__(container_id, *args, **kwargs)
self._empty_instance_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() self._container_registry = ContainerRegistry.getInstance()
self._empty_instance_container = self._container_registry.getEmptyInstanceContainer()
self._empty_quality_changes = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0]
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))]
self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes
self._containers[_ContainerIndexes.Quality] = self._empty_quality
self._containers[_ContainerIndexes.Material] = self._empty_material
self._containers[_ContainerIndexes.Variant] = self._empty_variant
self.containersChanged.connect(self._onContainersChanged) self.containersChanged.connect(self._onContainersChanged)
@ -110,7 +121,7 @@ class CuraContainerStack(ContainerStack):
# #
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setQualityById(self, new_quality_id: str) -> None: def setQualityById(self, new_quality_id: str) -> None:
quality = self._empty_instance_container quality = self._empty_quality
if new_quality_id == "default": if new_quality_id == "default":
new_quality = self.findDefaultQuality() new_quality = self.findDefaultQuality()
if new_quality: if new_quality:
@ -148,7 +159,7 @@ class CuraContainerStack(ContainerStack):
# #
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setMaterialById(self, new_material_id: str) -> None: def setMaterialById(self, new_material_id: str) -> None:
material = self._empty_instance_container material = self._empty_material
if new_material_id == "default": if new_material_id == "default":
new_material = self.findDefaultMaterial() new_material = self.findDefaultMaterial()
if new_material: if new_material:
@ -186,7 +197,7 @@ class CuraContainerStack(ContainerStack):
# #
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setVariantById(self, new_variant_id: str) -> None: def setVariantById(self, new_variant_id: str) -> None:
variant = self._empty_instance_container variant = self._empty_variant
if new_variant_id == "default": if new_variant_id == "default":
new_variant = self.findDefaultVariant() new_variant = self.findDefaultVariant()
if new_variant: if new_variant:
@ -348,8 +359,8 @@ class CuraContainerStack(ContainerStack):
# #
# \throws InvalidContainerStackError Raised when no definition can be found for the stack. # \throws InvalidContainerStackError Raised when no definition can be found for the stack.
@override(ContainerStack) @override(ContainerStack)
def deserialize(self, contents: str) -> None: def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
super().deserialize(contents) super().deserialize(contents, file_name)
new_containers = self._containers.copy() new_containers = self._containers.copy()
while len(new_containers) < len(_ContainerIndexes.IndexTypeMap): while len(new_containers) < len(_ContainerIndexes.IndexTypeMap):
@ -396,7 +407,9 @@ class CuraContainerStack(ContainerStack):
# \note This method assumes the stack has a valid machine definition. # \note This method assumes the stack has a valid machine definition.
def findDefaultVariant(self) -> Optional[ContainerInterface]: def findDefaultVariant(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition() definition = self._getMachineDefinition()
if not definition.getMetaDataEntry("has_variants"): # has_variants can be overridden in other containers and stacks.
# In the case of UM2, it is overridden in the GlobalStack
if not self.getMetaDataEntry("has_variants"):
# If the machine does not use variants, we should never set a variant. # If the machine does not use variants, we should never set a variant.
return None return None
@ -454,7 +467,7 @@ class CuraContainerStack(ContainerStack):
else: else:
search_criteria["definition"] = "fdmprinter" search_criteria["definition"] = "fdmprinter"
if self.material != self._empty_instance_container: if self.material != self._empty_material:
search_criteria["name"] = self.material.name search_criteria["name"] = self.material.name
else: else:
preferred_material = definition.getMetaDataEntry("preferred_material") preferred_material = definition.getMetaDataEntry("preferred_material")
@ -501,7 +514,7 @@ class CuraContainerStack(ContainerStack):
else: else:
search_criteria["definition"] = "fdmprinter" search_criteria["definition"] = "fdmprinter"
if self.quality != self._empty_instance_container: if self.quality != self._empty_quality:
search_criteria["name"] = self.quality.name search_criteria["name"] = self.quality.name
else: else:
preferred_quality = definition.getMetaDataEntry("preferred_quality") preferred_quality = definition.getMetaDataEntry("preferred_quality")

View file

@ -47,6 +47,24 @@ class CuraStackBuilder:
new_global_stack.setName(generated_name) new_global_stack.setName(generated_name)
extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId())
if not extruder_definition:
# create extruder stack for single extrusion machines that have no separate extruder definition files
extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0]
new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id)
new_extruder = cls.createExtruderStack(
new_extruder_id,
definition = extruder_definition,
machine_definition_id = machine_definition.getId(),
quality = "default",
material = "default",
variant = "default",
next_stack = new_global_stack
)
new_global_stack.addExtruder(new_extruder)
else:
# create extruder stack for each found extruder definition
for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id): for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id):
position = extruder_definition.getMetaDataEntry("position", None) position = extruder_definition.getMetaDataEntry("position", None)
if not position: if not position:
@ -62,6 +80,7 @@ class CuraStackBuilder:
variant = "default", variant = "default",
next_stack = new_global_stack next_stack = new_global_stack
) )
new_global_stack.addExtruder(new_extruder)
return new_global_stack return new_global_stack
@ -80,7 +99,9 @@ class CuraStackBuilder:
stack.setName(definition.getName()) stack.setName(definition.getName())
stack.setDefinition(definition) stack.setDefinition(definition)
stack.addMetaDataEntry("position", definition.getMetaDataEntry("position")) stack.addMetaDataEntry("position", definition.getMetaDataEntry("position"))
if "next_stack" in kwargs: #Add stacks before containers are added, since they may trigger a setting update.
if "next_stack" in kwargs:
# Add stacks before containers are added, since they may trigger a setting update.
stack.setNextStack(kwargs["next_stack"]) stack.setNextStack(kwargs["next_stack"])
user_container = InstanceContainer(new_stack_id + "_user") user_container = InstanceContainer(new_stack_id + "_user")

View file

@ -6,16 +6,13 @@ from UM.FlameProfiler import pyqtSlot
from UM.Application import Application # To get the global container stack to find the current machine. from UM.Application import Application # To get the global container stack to find the current machine.
from UM.Logger import Logger from UM.Logger import Logger
from UM.Decorators import deprecated
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID. from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from typing import Optional, List, TYPE_CHECKING, Union from typing import Optional, List, TYPE_CHECKING, Union
@ -28,6 +25,20 @@ if TYPE_CHECKING:
# #
# This keeps a list of extruder stacks for each machine. # This keeps a list of extruder stacks for each machine.
class ExtruderManager(QObject): class ExtruderManager(QObject):
## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None):
super().__init__(parent)
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
self._selected_object_extruders = []
self._global_container_stack_definition_id = None
self._addCurrentMachineExtruders()
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
## Signal to notify other components when the list of extruders for a machine definition changes. ## Signal to notify other components when the list of extruders for a machine definition changes.
extrudersChanged = pyqtSignal(QVariant) extrudersChanged = pyqtSignal(QVariant)
@ -38,18 +49,6 @@ class ExtruderManager(QObject):
## Notify when the user switches the currently active extruder. ## Notify when the user switches the currently active extruder.
activeExtruderChanged = pyqtSignal() activeExtruderChanged = pyqtSignal()
## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None):
super().__init__(parent)
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
self._selected_object_extruders = []
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
self._global_container_stack_definition_id = None
self._addCurrentMachineExtruders()
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
## Gets the unique identifier of the currently active extruder stack. ## Gets the unique identifier of the currently active extruder stack.
# #
# The currently active extruder stack is the stack that is currently being # The currently active extruder stack is the stack that is currently being
@ -76,23 +75,23 @@ class ExtruderManager(QObject):
return 0 return 0
## Gets a dict with the extruder stack ids with the extruder number as the key. ## Gets a dict with the extruder stack ids with the extruder number as the key.
# The key "-1" indicates the global stack id.
#
@pyqtProperty("QVariantMap", notify = extrudersChanged) @pyqtProperty("QVariantMap", notify = extrudersChanged)
def extruderIds(self): def extruderIds(self):
extruder_stack_ids = {} extruder_stack_ids = {}
global_stack_id = Application.getInstance().getGlobalContainerStack().getId() global_stack_id = Application.getInstance().getGlobalContainerStack().getId()
extruder_stack_ids["-1"] = global_stack_id
if global_stack_id in self._extruder_trains: if global_stack_id in self._extruder_trains:
for position in self._extruder_trains[global_stack_id]: for position in self._extruder_trains[global_stack_id]:
extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId() extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
return extruder_stack_ids return extruder_stack_ids
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def getQualityChangesIdByExtruderStackId(self, id: str) -> str: def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str:
for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]: for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position] extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
if extruder.getId() == id: if extruder.getId() == extruder_stack_id:
return extruder.qualityChanges.getId() return extruder.qualityChanges.getId()
## The instance of the singleton pattern. ## The instance of the singleton pattern.
@ -100,6 +99,10 @@ class ExtruderManager(QObject):
# It's None if the extruder manager hasn't been created yet. # It's None if the extruder manager hasn't been created yet.
__instance = None __instance = None
@staticmethod
def createExtruderManager():
return ExtruderManager().getInstance()
## Gets an instance of the extruder manager, or creates one if no instance ## Gets an instance of the extruder manager, or creates one if no instance
# exists yet. # exists yet.
# #
@ -185,6 +188,7 @@ class ExtruderManager(QObject):
if global_container_stack.getId() in self._extruder_trains: if global_container_stack.getId() in self._extruder_trains:
if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]: if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]:
return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)] return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)]
return None return None
## Get an extruder stack by index ## Get an extruder stack by index
@ -203,40 +207,6 @@ class ExtruderManager(QObject):
result.append(self.getExtruderStack(i)) result.append(self.getExtruderStack(i))
return result return result
## Adds all extruders of a specific machine definition to the extruder
# manager.
#
# \param machine_definition The machine definition to add the extruders for.
# \param machine_id The machine_id to add the extruders for.
@deprecated("Use CuraStackBuilder", "2.6")
def addMachineExtruders(self, machine_definition: DefinitionContainerInterface, machine_id: str) -> None:
changed = False
machine_definition_id = machine_definition.getId()
if machine_id not in self._extruder_trains:
self._extruder_trains[machine_id] = { }
changed = True
container_registry = ContainerRegistry.getInstance()
if container_registry:
# Add the extruder trains that don't exist yet.
for extruder_definition in container_registry.findDefinitionContainers(machine = machine_definition_id):
position = extruder_definition.getMetaDataEntry("position", None)
if not position:
Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.getId())
if not container_registry.findContainerStacksMetadata(machine = machine_id, position = position): # Doesn't exist yet.
self.createExtruderTrain(extruder_definition, machine_definition, position, machine_id)
changed = True
# Gets the extruder trains that we just created as well as any that still existed.
extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = machine_id)
for extruder_train in extruder_trains:
self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
# regardless of what the next stack is, we have to set it again, because of signal routing.
extruder_train.setNextStack(Application.getInstance().getGlobalContainerStack())
changed = True
if changed:
self.extrudersChanged.emit(machine_id)
def registerExtruder(self, extruder_train, machine_id): def registerExtruder(self, extruder_train, machine_id):
changed = False changed = False
@ -256,138 +226,6 @@ class ExtruderManager(QObject):
if changed: if changed:
self.extrudersChanged.emit(machine_id) self.extrudersChanged.emit(machine_id)
## Creates a container stack for an extruder train.
#
# The container stack has an extruder definition at the bottom, which is
# linked to a machine definition. Then it has a variant profile, a material
# profile, a quality profile and a user profile, in that order.
#
# The resulting container stack is added to the registry.
#
# \param extruder_definition The extruder to create the extruder train for.
# \param machine_definition The machine that the extruder train belongs to.
# \param position The position of this extruder train in the extruder slots of the machine.
# \param machine_id The id of the "global" stack this extruder is linked to.
@deprecated("Use CuraStackBuilder::createExtruderStack", "2.6")
def createExtruderTrain(self, extruder_definition: DefinitionContainerInterface, machine_definition: DefinitionContainerInterface,
position, machine_id: str) -> None:
# Cache some things.
container_registry = ContainerRegistry.getInstance()
machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition)
# Create a container stack for this extruder.
extruder_stack_id = container_registry.uniqueName(extruder_definition.getId())
container_stack = ContainerStack(extruder_stack_id)
container_stack.setName(extruder_definition.getName()) # Take over the display name to display the stack with.
container_stack.addMetaDataEntry("type", "extruder_train")
container_stack.addMetaDataEntry("machine", machine_id)
container_stack.addMetaDataEntry("position", position)
container_stack.addContainer(extruder_definition)
# Find the variant to use for this extruder.
variant = container_registry.findInstanceContainers(id = "empty_variant")[0]
if machine_definition.getMetaDataEntry("has_variants"):
# First add any variant. Later, overwrite with preference if the preference is valid.
variants = container_registry.findInstanceContainers(definition = machine_definition_id, type = "variant")
if len(variants) >= 1:
variant = variants[0]
preferred_variant_id = machine_definition.getMetaDataEntry("preferred_variant")
if preferred_variant_id:
preferred_variants = container_registry.findInstanceContainers(id = preferred_variant_id, definition = machine_definition_id, type = "variant")
if len(preferred_variants) >= 1:
variant = preferred_variants[0]
else:
Logger.log("w", "The preferred variant \"%s\" of machine %s doesn't exist or is not a variant profile.", preferred_variant_id, machine_id)
# And leave it at the default variant.
container_stack.addContainer(variant)
# Find a material to use for this variant.
material = container_registry.findInstanceContainers(id = "empty_material")[0]
if machine_definition.getMetaDataEntry("has_materials"):
# First add any material. Later, overwrite with preference if the preference is valid.
machine_has_variant_materials = machine_definition.getMetaDataEntry("has_variant_materials", default = False)
if machine_has_variant_materials or machine_has_variant_materials == "True":
materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id, variant = variant.getId())
else:
materials = container_registry.findInstanceContainers(type = "material", definition = machine_definition_id)
if len(materials) >= 1:
material = materials[0]
preferred_material_id = machine_definition.getMetaDataEntry("preferred_material")
if preferred_material_id:
global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
if global_stack:
approximate_material_diameter = str(round(global_stack[0].getProperty("material_diameter", "value")))
else:
approximate_material_diameter = str(round(machine_definition.getProperty("material_diameter", "value")))
search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter}
if machine_definition.getMetaDataEntry("has_machine_materials"):
search_criteria["definition"] = machine_definition_id
if machine_definition.getMetaDataEntry("has_variants") and variant:
search_criteria["variant"] = variant.id
else:
search_criteria["definition"] = "fdmprinter"
preferred_materials = container_registry.findInstanceContainers(**search_criteria)
if len(preferred_materials) >= 1:
# In some cases we get multiple materials. In that case, prefer materials that are marked as read only.
read_only_preferred_materials = [preferred_material for preferred_material in preferred_materials if container_registry.isReadOnly(preferred_material.getId())]
if len(read_only_preferred_materials) >= 1:
material = read_only_preferred_materials[0]
else:
material = preferred_materials[0]
else:
Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id)
# And leave it at the default material.
container_stack.addContainer(material)
# Find a quality to use for this extruder.
quality = container_registry.getEmptyInstanceContainer()
search_criteria = { "type": "quality" }
if machine_definition.getMetaDataEntry("has_machine_quality"):
search_criteria["definition"] = machine_definition_id
if machine_definition.getMetaDataEntry("has_materials") and material:
search_criteria["material"] = material.id
else:
search_criteria["definition"] = "fdmprinter"
preferred_quality = machine_definition.getMetaDataEntry("preferred_quality")
if preferred_quality:
search_criteria["id"] = preferred_quality
quality_containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if not quality_containers and preferred_quality:
Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id)
search_criteria.pop("id", None)
quality_containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if quality_containers:
quality = quality_containers[0]
container_stack.addContainer(quality)
empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
container_stack.addContainer(empty_quality_changes)
user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id)
if user_profile: # There was already a user profile, loaded from settings.
user_profile = user_profile[0]
else:
user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile.
user_profile.addMetaDataEntry("type", "user")
user_profile.addMetaDataEntry("extruder", extruder_stack_id)
from cura.CuraApplication import CuraApplication
user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_profile.setDefinition(machine_definition.getId())
container_registry.addContainer(user_profile)
container_stack.addContainer(user_profile)
# regardless of what the next stack is, we have to set it again, because of signal routing.
container_stack.setNextStack(Application.getInstance().getGlobalContainerStack())
container_registry.addContainer(container_stack)
def getAllExtruderValues(self, setting_key): def getAllExtruderValues(self, setting_key):
return self.getAllExtruderSettings(setting_key, "value") return self.getAllExtruderSettings(setting_key, "value")
@ -396,16 +234,12 @@ class ExtruderManager(QObject):
# \param setting_key \type{str} The setting to get the property of. # \param setting_key \type{str} The setting to get the property of.
# \param property \type{str} The property to get. # \param property \type{str} The property to get.
# \return \type{List} the list of results # \return \type{List} the list of results
def getAllExtruderSettings(self, setting_key, property): def getAllExtruderSettings(self, setting_key: str, prop: str):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack.getProperty("machine_extruder_count", "value") <= 1:
return [global_container_stack.getProperty(setting_key, property)]
result = [] result = []
for index in self.extruderIds: for index in self.extruderIds:
extruder_stack_id = self.extruderIds[str(index)] extruder_stack_id = self.extruderIds[str(index)]
stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0] extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
result.append(stack.getProperty(setting_key, property)) result.append(extruder_stack.getProperty(setting_key, prop))
return result return result
## Gets the extruder stacks that are actually being used at the moment. ## Gets the extruder stacks that are actually being used at the moment.
@ -422,20 +256,25 @@ class ExtruderManager(QObject):
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()
if global_stack.getProperty("machine_extruder_count", "value") <= 1: #For single extrusion.
return [global_stack]
used_extruder_stack_ids = set() used_extruder_stack_ids = set()
#Get the extruders of all meshes in the scene. # Get the extruders of all meshes in the scene
support_enabled = False support_enabled = False
support_bottom_enabled = False support_bottom_enabled = False
support_roof_enabled = False support_roof_enabled = False
scene_root = Application.getInstance().getController().getScene().getRoot() scene_root = Application.getInstance().getController().getScene().getRoot()
meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] #Only use the nodes that will be printed.
# If no extruders are registered in the extruder manager yet, return an empty array
if len(self.extruderIds) == 0:
return []
# Get the extruders of all printable meshes in the scene
meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()]
for mesh in meshes: for mesh in meshes:
extruder_stack_id = mesh.callDecoration("getActiveExtruder") extruder_stack_id = mesh.callDecoration("getActiveExtruder")
if not extruder_stack_id: #No per-object settings for this node. if not extruder_stack_id:
# No per-object settings for this node
extruder_stack_id = self.extruderIds["0"] extruder_stack_id = self.extruderIds["0"]
used_extruder_stack_ids.add(extruder_stack_id) used_extruder_stack_ids.add(extruder_stack_id)
@ -474,6 +313,7 @@ class ExtruderManager(QObject):
# The platform adhesion extruder. Not used if using none. # The platform adhesion extruder. Not used if using none.
if global_stack.getProperty("adhesion_type", "value") != "none": if global_stack.getProperty("adhesion_type", "value") != "none":
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))]) used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))])
try: try:
return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids] return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids]
except IndexError: # One or more of the extruders was not found. except IndexError: # One or more of the extruders was not found.
@ -520,10 +360,6 @@ class ExtruderManager(QObject):
result = [] result = []
machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value") machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
# In case the printer is using one extruder, shouldn't exist active extruder stacks
if machine_extruder_count == 1:
return result
if global_stack and global_stack.getId() in self._extruder_trains: if global_stack and global_stack.getId() in self._extruder_trains:
for extruder in sorted(self._extruder_trains[global_stack.getId()]): for extruder in sorted(self._extruder_trains[global_stack.getId()]):
result.append(self._extruder_trains[global_stack.getId()][extruder]) result.append(self._extruder_trains[global_stack.getId()][extruder])
@ -536,24 +372,39 @@ class ExtruderManager(QObject):
self._global_container_stack_definition_id = global_container_stack.getBottom().getId() self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
self.globalContainerStackDefinitionChanged.emit() self.globalContainerStackDefinitionChanged.emit()
# If the global container changed, the number of extruders could be changed and so the active_extruder_index is updated # If the global container changed, the machine changed and might have extruders that were not registered yet
extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") self._addCurrentMachineExtruders()
if extruder_count > 1:
if self._active_extruder_index == -1:
self.setActiveExtruderIndex(0)
else:
if self._active_extruder_index != -1:
self.setActiveExtruderIndex(-1)
self.activeExtruderChanged.emit()
self.resetSelectedObjectExtruders() self.resetSelectedObjectExtruders()
## Adds the extruders of the currently active machine. ## Adds the extruders of the currently active machine.
def _addCurrentMachineExtruders(self) -> None: def _addCurrentMachineExtruders(self) -> None:
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
if global_stack and global_stack.getBottom(): extruders_changed = False
self.addMachineExtruders(global_stack.getBottom(), global_stack.getId())
if global_stack:
container_registry = ContainerRegistry.getInstance()
global_stack_id = global_stack.getId()
# Gets the extruder trains that we just created as well as any that still existed.
extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = global_stack_id)
# Make sure the extruder trains for the new machine can be placed in the set of sets
if global_stack_id not in self._extruder_trains:
self._extruder_trains[global_stack_id] = {}
extruders_changed = True
# Register the extruder trains by position
for extruder_train in extruder_trains:
self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train
# regardless of what the next stack is, we have to set it again, because of signal routing. ???
extruder_train.setNextStack(global_stack)
extruders_changed = True
if extruders_changed:
self.extrudersChanged.emit(global_stack_id)
self.setActiveExtruderIndex(0)
## Get all extruder values for a certain setting. ## Get all extruder values for a certain setting.
# #
@ -632,7 +483,7 @@ class ExtruderManager(QObject):
# #
# This is exposed to qml for display purposes # This is exposed to qml for display purposes
# #
# \param key The key of the setting to retieve values for. # \param key The key of the setting to retrieve values for.
# #
# \return String representing the extruder values # \return String representing the extruder values
@pyqtSlot(str, result="QVariant") @pyqtSlot(str, result="QVariant")
@ -656,7 +507,8 @@ class ExtruderManager(QObject):
value = extruder.getRawProperty(key, "value") value = extruder.getRawProperty(key, "value")
if isinstance(value, SettingFunction): if isinstance(value, SettingFunction):
value = value(extruder) value = value(extruder)
else: #Just a value from global. else:
# Just a value from global.
value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value") value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value")
return value return value

View file

@ -92,8 +92,8 @@ class ExtruderStack(CuraContainerStack):
return self.getNextStack()._getMachineDefinition() return self.getNextStack()._getMachineDefinition()
@override(CuraContainerStack) @override(CuraContainerStack)
def deserialize(self, contents: str) -> None: def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
super().deserialize(contents) super().deserialize(contents, file_name)
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", "")) stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
if stacks: if stacks:
self.setNextStack(stacks[0]) self.setNextStack(stacks[0])
@ -115,6 +115,11 @@ class ExtruderStack(CuraContainerStack):
if has_global_dependencies: if has_global_dependencies:
self.getNextStack().propertiesChanged.emit(key, properties) self.getNextStack().propertiesChanged.emit(key, properties)
def findDefaultVariant(self):
# The default variant is defined in the machine stack and/or definition, so use the machine stack to find
# the default variant.
return self.getNextStack().findDefaultVariant()
extruder_stack_mime = MimeType( extruder_stack_mime = MimeType(
name = "application/x-cura-extruderstack", name = "application/x-cura-extruderstack",

View file

@ -74,10 +74,10 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self._active_machine_extruders = [] # type: Iterable[ExtruderStack] self._active_machine_extruders = [] # type: Iterable[ExtruderStack]
self._add_optional_extruder = False self._add_optional_extruder = False
#Listen to changes. # Listen to changes
Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) #When the machine is swapped we must update the active machine extruders. Application.getInstance().globalContainerStackChanged.connect(self._extrudersChanged) # When the machine is swapped we must update the active machine extruders
ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) #When the extruders change we must link to the stack-changed signal of the new extruder. ExtruderManager.getInstance().extrudersChanged.connect(self._extrudersChanged) # When the extruders change we must link to the stack-changed signal of the new extruder
self._extrudersChanged() #Also calls _updateExtruders. self._extrudersChanged() # Also calls _updateExtruders
def setAddGlobal(self, add): def setAddGlobal(self, add):
if add != self._add_global: if add != self._add_global:
@ -128,14 +128,17 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
def _extrudersChanged(self, machine_id = None): def _extrudersChanged(self, machine_id = None):
if machine_id is not None: if machine_id is not None:
if Application.getInstance().getGlobalContainerStack() is None: if Application.getInstance().getGlobalContainerStack() is None:
return #No machine, don't need to update the current machine's extruders. # No machine, don't need to update the current machine's extruders
return
if machine_id != Application.getInstance().getGlobalContainerStack().getId(): if machine_id != Application.getInstance().getGlobalContainerStack().getId():
return #Not the current machine. # Not the current machine
#Unlink from old extruders. return
# Unlink from old extruders
for extruder in self._active_machine_extruders: for extruder in self._active_machine_extruders:
extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged) extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged)
#Link to new extruders. # Link to new extruders
self._active_machine_extruders = [] self._active_machine_extruders = []
extruder_manager = ExtruderManager.getInstance() extruder_manager = ExtruderManager.getInstance()
for extruder in extruder_manager.getExtruderStacks(): for extruder in extruder_manager.getExtruderStacks():
@ -150,7 +153,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name # The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
self._updateExtruders() self._updateExtruders()
modelChanged = pyqtSignal() modelChanged = pyqtSignal()
def _updateExtruders(self): def _updateExtruders(self):
@ -161,14 +163,17 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# This should be called whenever the list of extruders changes. # This should be called whenever the list of extruders changes.
@UM.FlameProfiler.profile @UM.FlameProfiler.profile
def __updateExtruders(self): def __updateExtruders(self):
changed = False extruders_changed = False
if self.rowCount() != 0: if self.rowCount() != 0:
changed = True extruders_changed = True
items = [] items = []
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
# TODO: remove this - CURA-4482
if self._add_global: if self._add_global:
material = global_container_stack.material material = global_container_stack.material
color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0] color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0]
@ -180,40 +185,44 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
"definition": "" "definition": ""
} }
items.append(item) items.append(item)
changed = True extruders_changed = True
# get machine extruder count for verification
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value") machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
manager = ExtruderManager.getInstance()
for extruder in manager.getMachineExtruders(global_container_stack.getId()): for extruder in ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()):
position = extruder.getMetaDataEntry("position", default = "0") # Get the position position = extruder.getMetaDataEntry("position", default = "0") # Get the position
try: try:
position = int(position) position = int(position)
except ValueError: #Not a proper int. except ValueError:
# Not a proper int.
position = -1 position = -1
if position >= machine_extruder_count: if position >= machine_extruder_count:
continue continue
extruder_name = extruder.getName()
material = extruder.material
variant = extruder.variant
default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0] default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0]
color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color color = extruder.material.getMetaDataEntry("color_code", default = default_color) if extruder.material else default_color
item = { #Construct an item with only the relevant information.
# construct an item with only the relevant information
item = {
"id": extruder.getId(), "id": extruder.getId(),
"name": extruder_name, "name": extruder.getName(),
"color": color, "color": color,
"index": position, "index": position,
"definition": extruder.getBottom().getId(), "definition": extruder.getBottom().getId(),
"material": material.getName() if material else "", "material": extruder.material.getName() if extruder.material else "",
"variant": variant.getName() if variant else "", "variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core
} }
items.append(item)
changed = True
if changed: items.append(item)
extruders_changed = True
if extruders_changed:
# sort by extruder index
items.sort(key = lambda i: i["index"]) items.sort(key = lambda i: i["index"])
# We need optional extruder to be last, so add it after we do sorting. # We need optional extruder to be last, so add it after we do sorting.
# This way we can simply intrepret the -1 of the index as the last item (which it now always is) # This way we can simply interpret the -1 of the index as the last item (which it now always is)
if self._add_optional_extruder: if self._add_optional_extruder:
item = { item = {
"id": "", "id": "",
@ -223,5 +232,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
"definition": "" "definition": ""
} }
items.append(item) items.append(item)
self.setItems(items) self.setItems(items)
self.modelChanged.emit() self.modelChanged.emit()

View file

@ -25,7 +25,7 @@ class GlobalStack(CuraContainerStack):
self.addMetaDataEntry("type", "machine") # For backward compatibility self.addMetaDataEntry("type", "machine") # For backward compatibility
self._extruders = {} self._extruders = {} # type: Dict[str, "ExtruderStack"]
# This property is used to track which settings we are calculating the "resolve" for # This property is used to track which settings we are calculating the "resolve" for
# and if so, to bypass the resolve to prevent an infinite recursion that would occur # and if so, to bypass the resolve to prevent an infinite recursion that would occur
@ -57,13 +57,6 @@ class GlobalStack(CuraContainerStack):
# \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we # \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we
# already have the maximum number of extruders. # already have the maximum number of extruders.
def addExtruder(self, extruder: ContainerStack) -> None: def addExtruder(self, extruder: ContainerStack) -> None:
extruder_count = self.getProperty("machine_extruder_count", "value")
if extruder_count <= 1:
Logger.log("i", "Not adding extruder[%s] to [%s] because it is a single-extrusion machine.",
extruder.id, self.id)
return
position = extruder.getMetaDataEntry("position") position = extruder.getMetaDataEntry("position")
if position is None: if position is None:
Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id) Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id)

View file

@ -47,6 +47,11 @@ class MachineManager(QObject):
self._active_container_stack = None # type: CuraContainerStack self._active_container_stack = None # type: CuraContainerStack
self._global_container_stack = None # type: GlobalStack self._global_container_stack = None # type: GlobalStack
# Used to store the new containers until after confirming the dialog
self._new_variant_container = None
self._new_material_container = None
self._new_quality_containers = []
self._error_check_timer = QTimer() self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250) self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True) self._error_check_timer.setSingleShot(True)
@ -58,6 +63,7 @@ class MachineManager(QObject):
self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged) self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
## When the global container is changed, active material probably needs to be updated. ## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged) self.globalContainerChanged.connect(self.activeMaterialChanged)
self.globalContainerChanged.connect(self.activeVariantChanged) self.globalContainerChanged.connect(self.activeVariantChanged)
@ -65,10 +71,10 @@ class MachineManager(QObject):
self._stacks_have_errors = None self._stacks_have_errors = None
self._empty_variant_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0]
self._empty_material_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0]
self._empty_quality_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
self._empty_quality_changes_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() self._empty_quality_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0]
self._onGlobalContainerChanged() self._onGlobalContainerChanged()
@ -84,6 +90,9 @@ class MachineManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged) ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged)
self.activeStackChanged.connect(self.activeStackValueChanged) self.activeStackChanged.connect(self.activeStackValueChanged)
# when a user closed dialog check if any delayed material or variant changes need to be applied
Application.getInstance().onDiscardOrKeepProfileChangesClosed.connect(self._executeDelayedActiveContainerStackChanges)
Preferences.getInstance().addPreference("cura/active_machine", "") Preferences.getInstance().addPreference("cura/active_machine", "")
self._global_event_keys = set() self._global_event_keys = set()
@ -98,7 +107,6 @@ class MachineManager(QObject):
if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id): if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
# An active machine was saved, so restore it. # An active machine was saved, so restore it.
self.setActiveMachine(active_machine_id) self.setActiveMachine(active_machine_id)
if self._global_container_stack and self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
# Make sure _active_container_stack is properly initiated # Make sure _active_container_stack is properly initiated
ExtruderManager.getInstance().setActiveExtruderIndex(0) ExtruderManager.getInstance().setActiveExtruderIndex(0)
@ -139,6 +147,14 @@ class MachineManager(QObject):
self.outputDevicesChanged.emit() self.outputDevicesChanged.emit()
@property
def newVariant(self):
return self._new_variant_container
@property
def newMaterial(self):
return self._new_material_container
@pyqtProperty("QVariantList", notify = outputDevicesChanged) @pyqtProperty("QVariantList", notify = outputDevicesChanged)
def printerOutputDevices(self): def printerOutputDevices(self):
return self._printer_output_devices return self._printer_output_devices
@ -257,53 +273,39 @@ class MachineManager(QObject):
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
except TypeError: except TypeError:
pass pass
material = self._global_container_stack.material
material.nameChanged.disconnect(self._onMaterialNameChanged)
quality = self._global_container_stack.quality
quality.nameChanged.disconnect(self._onQualityNameChanged)
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_stack.propertyChanged.disconnect(self._onPropertyChanged) extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged) extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
# update the local global container stack reference
self._global_container_stack = Application.getInstance().getGlobalContainerStack() self._global_container_stack = Application.getInstance().getGlobalContainerStack()
self.globalContainerChanged.emit() self.globalContainerChanged.emit()
# after switching the global stack we reconnect all the signals and set the variant and material references
if self._global_container_stack: if self._global_container_stack:
Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId()) Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId())
self._global_container_stack.nameChanged.connect(self._onMachineNameChanged) self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged) self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
self._global_container_stack.propertyChanged.connect(self._onPropertyChanged) self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1: # set the global variant to empty as we now use the extruder stack at all times - CURA-4482
# For multi-extrusion machines, we do not want variant or material profiles in the stack,
# because these are extruder specific and may cause wrong values to be used for extruders
# that did not specify a value in the extruder.
global_variant = self._global_container_stack.variant global_variant = self._global_container_stack.variant
if global_variant != self._empty_variant_container: if global_variant != self._empty_variant_container:
self._global_container_stack.setVariant(self._empty_variant_container) self._global_container_stack.setVariant(self._empty_variant_container)
# set the global material to empty as we now use the extruder stack at all times - CURA-4482
global_material = self._global_container_stack.material global_material = self._global_container_stack.material
if global_material != self._empty_material_container: if global_material != self._empty_material_container:
self._global_container_stack.setMaterial(self._empty_material_container) self._global_container_stack.setMaterial(self._empty_material_container)
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): #Listen for changes on all extruder stacks. # Listen for changes on all extruder stacks
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_stack.propertyChanged.connect(self._onPropertyChanged) extruder_stack.propertyChanged.connect(self._onPropertyChanged)
extruder_stack.containersChanged.connect(self._onInstanceContainersChanged) extruder_stack.containersChanged.connect(self._onInstanceContainersChanged)
else:
material = self._global_container_stack.material
material.nameChanged.connect(self._onMaterialNameChanged)
quality = self._global_container_stack.quality
quality.nameChanged.connect(self._onQualityNameChanged)
self._active_container_stack = self._global_container_stack
self.activeStackChanged.emit()
self._error_check_timer.start() self._error_check_timer.start()
## Update self._stacks_valid according to _checkStacksForErrors and emit if change. ## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
@ -319,8 +321,6 @@ class MachineManager(QObject):
old_active_container_stack = self._active_container_stack old_active_container_stack = self._active_container_stack
self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack() self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
if not self._active_container_stack:
self._active_container_stack = self._global_container_stack
self._error_check_timer.start() self._error_check_timer.start()
@ -333,6 +333,7 @@ class MachineManager(QObject):
self.activeQualityChanged.emit() self.activeQualityChanged.emit()
self.activeVariantChanged.emit() self.activeVariantChanged.emit()
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine
self._error_check_timer.start() self._error_check_timer.start()
def _onInstanceContainersChanged(self, container): def _onInstanceContainersChanged(self, container):
@ -349,6 +350,8 @@ class MachineManager(QObject):
@pyqtSlot(str) @pyqtSlot(str)
def setActiveMachine(self, stack_id: str) -> None: def setActiveMachine(self, stack_id: str) -> None:
self.blurSettings.emit() # Ensure no-one has focus. self.blurSettings.emit() # Ensure no-one has focus.
self._cancelDelayedActiveContainerStackChanges()
containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id) containers = ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
if containers: if containers:
Application.getInstance().setGlobalContainerStack(containers[0]) Application.getInstance().setGlobalContainerStack(containers[0])
@ -364,15 +367,6 @@ class MachineManager(QObject):
else: else:
Logger.log("w", "Failed creating a new machine!") Logger.log("w", "Failed creating a new machine!")
## Create a name that is not empty and unique
# \param container_type \type{string} Type of the container (machine, quality, ...)
# \param current_name \type{} Current name of the container, which may be an acceptable option
# \param new_name \type{string} Base name, which may not be unique
# \param fallback_name \type{string} Name to use when (stripped) new_name is empty
# \return \type{string} Name that is unique for the specified type and name/id
def _createUniqueName(self, container_type: str, current_name: str, new_name: str, fallback_name: str) -> str:
return ContainerRegistry.getInstance().createUniqueName(container_type, current_name, new_name, fallback_name)
def _checkStacksHaveErrors(self) -> bool: def _checkStacksHaveErrors(self) -> bool:
if self._global_container_stack is None: #No active machine. if self._global_container_stack is None: #No active machine.
return False return False
@ -626,12 +620,17 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeQualityChanged) @pyqtProperty(str, notify=activeQualityChanged)
def activeQualityId(self) -> str: def activeQualityId(self) -> str:
if self._active_container_stack: if self._active_container_stack:
quality = self._active_container_stack.qualityChanges
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
return quality.getId()
quality = self._active_container_stack.quality quality = self._active_container_stack.quality
if quality: if isinstance(quality, type(self._empty_quality_container)):
return ""
quality_changes = self._active_container_stack.qualityChanges
if quality and quality_changes:
if isinstance(quality_changes, type(self._empty_quality_changes_container)):
# It's a built-in profile
return quality.getId() return quality.getId()
else:
# Custom profile
return quality_changes.getId()
return "" return ""
@pyqtProperty(str, notify=activeQualityChanged) @pyqtProperty(str, notify=activeQualityChanged)
@ -696,9 +695,9 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeQualityChanged) @pyqtProperty(str, notify = activeQualityChanged)
def activeQualityChangesId(self) -> str: def activeQualityChangesId(self) -> str:
if self._active_container_stack: if self._active_container_stack:
changes = self._active_container_stack.qualityChanges quality_changes = self._active_container_stack.qualityChanges
if changes and changes.getId() != "empty": if quality_changes and not isinstance(quality_changes, type(self._empty_quality_changes_container)):
return changes.getId() return quality_changes.getId()
return "" return ""
## Check if a container is read_only ## Check if a container is read_only
@ -709,15 +708,13 @@ class MachineManager(QObject):
## Copy the value of the setting of the current extruder to all other extruders as well as the global container. ## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
@pyqtSlot(str) @pyqtSlot(str)
def copyValueToExtruders(self, key: str): def copyValueToExtruders(self, key: str):
if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1:
return
new_value = self._active_container_stack.getProperty(key, "value") new_value = self._active_container_stack.getProperty(key, "value")
stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
stacks.append(self._global_container_stack)
for extruder_stack in stacks: # check in which stack the value has to be replaced
for extruder_stack in extruder_stacks:
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
extruder_stack.getTop().setProperty(key, "value", new_value) extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
## Set the active material by switching out a container ## Set the active material by switching out a container
# Depending on from/to material+current variant, a quality profile is chosen and set. # Depending on from/to material+current variant, a quality profile is chosen and set.
@ -744,7 +741,7 @@ class MachineManager(QObject):
self.blurSettings.emit() self.blurSettings.emit()
old_material.nameChanged.disconnect(self._onMaterialNameChanged) old_material.nameChanged.disconnect(self._onMaterialNameChanged)
self._active_container_stack.material = material_container self._new_material_container = material_container # self._active_container_stack will be updated with a delay
Logger.log("d", "Active material changed") Logger.log("d", "Active material changed")
material_container.nameChanged.connect(self._onMaterialNameChanged) material_container.nameChanged.connect(self._onMaterialNameChanged)
@ -763,13 +760,14 @@ class MachineManager(QObject):
quality_type = old_quality_changes.getMetaDataEntry("quality_type") quality_type = old_quality_changes.getMetaDataEntry("quality_type")
new_quality_id = old_quality_changes.getId() new_quality_id = old_quality_changes.getId()
# See if the requested quality type is available in the new situation. global_stack = Application.getInstance().getGlobalContainerStack()
machine_definition = self._active_container_stack.getBottom() if global_stack:
quality_manager = QualityManager.getInstance() quality_manager = QualityManager.getInstance()
candidate_quality = None candidate_quality = None
if quality_type: if quality_type:
candidate_quality = quality_manager.findQualityByQualityType(quality_type, candidate_quality = quality_manager.findQualityByQualityType(quality_type,
quality_manager.getWholeMachineDefinition(machine_definition), quality_manager.getWholeMachineDefinition(global_stack.definition),
[material_container]) [material_container])
if not candidate_quality or isinstance(candidate_quality, type(self._empty_quality_changes_container)): if not candidate_quality or isinstance(candidate_quality, type(self._empty_quality_changes_container)):
@ -798,13 +796,13 @@ class MachineManager(QObject):
old_material = self._active_container_stack.material old_material = self._active_container_stack.material
if old_variant: if old_variant:
self.blurSettings.emit() self.blurSettings.emit()
self._active_container_stack.variant = containers[0] self._new_variant_container = containers[0] # self._active_container_stack will be updated with a delay
Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId())) Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
preferred_material_name = None preferred_material_name = None
if old_material: if old_material:
preferred_material_name = old_material.getName() preferred_material_name = old_material.getName()
preferred_material_id = self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id) self.setActiveMaterial(preferred_material_id)
else: else:
Logger.log("w", "While trying to set the active variant, no variant was found to replace.") Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
@ -819,8 +817,6 @@ class MachineManager(QObject):
if not containers or not self._global_container_stack: if not containers or not self._global_container_stack:
return return
Logger.log("d", "Attempting to change the active quality to %s", quality_id)
# Quality profile come in two flavours: type=quality and type=quality_changes # Quality profile come in two flavours: type=quality and type=quality_changes
# If we found a quality_changes profile then look up its parent quality profile. # If we found a quality_changes profile then look up its parent quality profile.
container_type = containers[0].get("type") container_type = containers[0].get("type")
@ -840,30 +836,74 @@ class MachineManager(QObject):
if new_quality_settings_list is None: if new_quality_settings_list is None:
return return
name_changed_connect_stacks = [] # Connect these stacks to the name changed callback # check if any of the stacks have a not supported profile
# if that is the case, all stacks should have a not supported state (otherwise it will show quality_type normal)
has_not_supported_quality = False
# check all stacks for not supported
for setting_info in new_quality_settings_list:
if setting_info["quality"].getMetaDataEntry("quality_type") == "not_supported":
has_not_supported_quality = True
break
# set all stacks to not supported if that's the case
if has_not_supported_quality:
for setting_info in new_quality_settings_list:
setting_info["quality"] = self._empty_quality_container
self._new_quality_containers.clear()
# store the upcoming quality profile changes per stack for later execution
# this prevents re-slicing before the user has made a choice in the discard or keep dialog
# (see _executeDelayedActiveContainerStackChanges)
for setting_info in new_quality_settings_list: for setting_info in new_quality_settings_list:
stack = setting_info["stack"] stack = setting_info["stack"]
stack_quality = setting_info["quality"] stack_quality = setting_info["quality"]
stack_quality_changes = setting_info["quality_changes"] stack_quality_changes = setting_info["quality_changes"]
name_changed_connect_stacks.append(stack_quality) self._new_quality_containers.append({
name_changed_connect_stacks.append(stack_quality_changes) "stack": stack,
self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True) "quality": stack_quality,
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True) "quality_changes": stack_quality_changes
})
# Send emits that are postponed in replaceContainer. # show the keep/discard dialog after the containers have been switched. Otherwise, the default values on
# Here the stacks are finished replacing and every value can be resolved based on the current state. # the dialog will be the those before the switching.
for setting_info in new_quality_settings_list: self._executeDelayedActiveContainerStackChanges()
setting_info["stack"].sendPostponedEmits()
# Connect to onQualityNameChanged
for stack in name_changed_connect_stacks:
stack.nameChanged.connect(self._onQualityNameChanged)
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
self._askUserToKeepOrClearCurrentSettings() Application.getInstance().discardOrKeepProfileChanges()
self.activeQualityChanged.emit() ## Used to update material and variant in the active container stack with a delay.
# This delay prevents the stack from triggering a lot of signals (eventually resulting in slicing)
# before the user decided to keep or discard any of their changes using the dialog.
# The Application.onDiscardOrKeepProfileChangesClosed signal triggers this method.
def _executeDelayedActiveContainerStackChanges(self):
if self._new_variant_container is not None:
self._active_container_stack.variant = self._new_variant_container
self._new_variant_container = None
if self._new_material_container is not None:
self._active_container_stack.material = self._new_material_container
self._new_material_container = None
# apply the new quality to all stacks
if self._new_quality_containers:
for new_quality in self._new_quality_containers:
self._replaceQualityOrQualityChangesInStack(new_quality["stack"], new_quality["quality"], postpone_emit = True)
self._replaceQualityOrQualityChangesInStack(new_quality["stack"], new_quality["quality_changes"], postpone_emit = True)
for new_quality in self._new_quality_containers:
new_quality["stack"].nameChanged.connect(self._onQualityNameChanged)
new_quality["stack"].sendPostponedEmits() # Send the signals that were postponed in _replaceQualityOrQualityChangesInStack
self._new_quality_containers.clear()
## Cancel set changes for material and variant in the active container stack.
# Used for ignoring any changes when switching between printers (setActiveMachine)
def _cancelDelayedActiveContainerStackChanges(self):
self._new_material_container = None
self._new_variant_container = None
## Determine the quality and quality changes settings for the current machine for a quality name. ## Determine the quality and quality changes settings for the current machine for a quality name.
# #
@ -877,29 +917,46 @@ class MachineManager(QObject):
global_container_stack = self._global_container_stack global_container_stack = self._global_container_stack
if not global_container_stack: if not global_container_stack:
return [] return []
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom())
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
if extruder_stacks:
stacks = extruder_stacks
else:
stacks = [global_container_stack]
for stack in stacks: # find qualities for extruders
material = stack.material for extruder_stack in extruder_stacks:
material = extruder_stack.material
# TODO: fix this
if self._new_material_container and extruder_stack.getId() == self._active_container_stack.getId():
material = self._new_material_container
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile is found for this quality type.
quality = self._empty_quality_container
result.append({"stack": stack, "quality": quality, "quality_changes": empty_quality_changes})
if extruder_stacks: if not quality:
# Add an extra entry for the global stack. # No quality profile is found for this quality type.
quality = self._empty_quality_container
result.append({
"stack": extruder_stack,
"quality": quality,
"quality_changes": empty_quality_changes
})
# also find a global quality for the machine
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True") global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True")
# if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482
if not global_quality and len(extruder_stacks) == 1:
global_quality = result[0]["quality"]
# if there is still no global quality, set it to empty (not supported)
if not global_quality: if not global_quality:
global_quality = self._empty_quality_container global_quality = self._empty_quality_container
result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": empty_quality_changes}) result.append({
"stack": global_container_stack,
"quality": global_quality,
"quality_changes": empty_quality_changes
})
return result return result
@ -912,10 +969,8 @@ class MachineManager(QObject):
quality_manager = QualityManager.getInstance() quality_manager = QualityManager.getInstance()
global_container_stack = self._global_container_stack global_container_stack = self._global_container_stack
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name, global_machine_definition)
quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name,
global_machine_definition)
global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None] global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None]
if global_quality_changes: if global_quality_changes:
@ -923,26 +978,25 @@ class MachineManager(QObject):
else: else:
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name) Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
return None return None
material = global_container_stack.material material = global_container_stack.material
# find a quality type that matches both machine and materials
if self._new_material_container and self._active_container_stack.getId() == global_container_stack.getId():
material = self._new_material_container
# For the global stack, find a quality which matches the quality_type in # For the global stack, find a quality which matches the quality_type in
# the quality changes profile and also satisfies any material constraints. # the quality changes profile and also satisfies any material constraints.
quality_type = global_quality_changes.getMetaDataEntry("quality_type") quality_type = global_quality_changes.getMetaDataEntry("quality_type")
if global_container_stack.getProperty("machine_extruder_count", "value") > 1:
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = True)
else:
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not global_quality:
global_quality = self._empty_quality_container
# Find the values for each extruder.
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
for stack in extruder_stacks: # append the extruder quality changes
extruder_definition = quality_manager.getParentMachineDefinition(stack.getBottom()) for extruder_stack in extruder_stacks:
extruder_definition = quality_manager.getParentMachineDefinition(extruder_stack.definition)
quality_changes_list = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()]
quality_changes_list = [qcp for qcp in quality_changes_profiles
if qcp.getMetaDataEntry("extruder") == extruder_definition.getId()]
if quality_changes_list: if quality_changes_list:
quality_changes = quality_changes_list[0] quality_changes = quality_changes_list[0]
else: else:
@ -950,20 +1004,39 @@ class MachineManager(QObject):
if not quality_changes: if not quality_changes:
quality_changes = self._empty_quality_changes_container quality_changes = self._empty_quality_changes_container
material = stack.material material = extruder_stack.material
if self._new_material_container and self._active_container_stack.getId() == extruder_stack.getId():
material = self._new_material_container
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile found for this quality type.
if not quality:
# No quality profile found for this quality type.
quality = self._empty_quality_container quality = self._empty_quality_container
result.append({"stack": stack, "quality": quality, "quality_changes": quality_changes}) result.append({
"stack": extruder_stack,
"quality": quality,
"quality_changes": quality_changes
})
if extruder_stacks: # append the global quality changes
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True") global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True")
# if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482
if not global_quality and len(extruder_stacks) == 1:
global_quality = result[0]["quality"]
# if still no global quality changes are found we set it to empty (not supported)
if not global_quality: if not global_quality:
global_quality = self._empty_quality_container global_quality = self._empty_quality_container
result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes})
else: result.append({
result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes}) "stack": global_container_stack,
"quality": global_quality,
"quality_changes": global_quality_changes
})
return result return result
@ -982,9 +1055,6 @@ class MachineManager(QObject):
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged) stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
self._onQualityNameChanged() self._onQualityNameChanged()
def _askUserToKeepOrClearCurrentSettings(self):
Application.getInstance().discardOrKeepProfileChanges()
@pyqtProperty(str, notify = activeVariantChanged) @pyqtProperty(str, notify = activeVariantChanged)
def activeVariantName(self) -> str: def activeVariantName(self) -> str:
if self._active_container_stack: if self._active_container_stack:
@ -1075,10 +1145,11 @@ class MachineManager(QObject):
@pyqtSlot(str, str) @pyqtSlot(str, str)
def renameMachine(self, machine_id: str, new_name: str): def renameMachine(self, machine_id: str, new_name: str):
containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id) container_registry = ContainerRegistry.getInstance()
if containers: machine_stack = container_registry.findContainerStacks(id = machine_id)
new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName()) if machine_stack:
containers[0].setName(new_name) new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].getBottom().getName())
machine_stack[0].setName(new_name)
self.globalContainerChanged.emit() self.globalContainerChanged.emit()
@pyqtSlot(str) @pyqtSlot(str)
@ -1102,15 +1173,14 @@ class MachineManager(QObject):
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
def hasMaterials(self) -> bool: def hasMaterials(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_materials", False)) return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False))
return False return False
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
def hasVariants(self) -> bool: def hasVariants(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_variants", False)) return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variants", False))
return False return False
## Property to indicate if a machine has "specialized" material profiles. ## Property to indicate if a machine has "specialized" material profiles.
@ -1118,8 +1188,7 @@ class MachineManager(QObject):
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
def filterMaterialsByMachine(self) -> bool: def filterMaterialsByMachine(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False)) return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False))
return False return False
## Property to indicate if a machine has "specialized" quality profiles. ## Property to indicate if a machine has "specialized" quality profiles.
@ -1127,7 +1196,7 @@ class MachineManager(QObject):
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
def filterQualityByMachine(self) -> bool: def filterQualityByMachine(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False)) return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False))
return False return False
## Get the Definition ID of a machine (specified by ID) ## Get the Definition ID of a machine (specified by ID)
@ -1140,7 +1209,7 @@ class MachineManager(QObject):
return containers[0].getBottom().getId() return containers[0].getBottom().getId()
@staticmethod @staticmethod
def createMachineManager(engine=None, script_engine=None): def createMachineManager():
return MachineManager() return MachineManager()
@deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7") @deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7")

View file

@ -19,3 +19,7 @@ class MaterialsModel(InstanceContainersModel):
def _onContainerMetaDataChanged(self, container): def _onContainerMetaDataChanged(self, container):
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed. if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
self._update() self._update()
def _onContainerChanged(self, container):
if container.getMetaDataEntry("type", "") == "material":
super()._onContainerChanged(container)

View file

@ -12,6 +12,11 @@ from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
from cura.QualityManager import QualityManager from cura.QualityManager import QualityManager
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from typing import List, TYPE_CHECKING
if TYPE_CHECKING:
from cura.Settings.ExtruderStack import ExtruderStack
## QML Model for listing the current list of valid quality profiles. ## QML Model for listing the current list of valid quality profiles.
# #
@ -27,7 +32,6 @@ class ProfilesModel(InstanceContainersModel):
self.addRoleName(self.AvailableRole, "available") self.addRoleName(self.AvailableRole, "available")
Application.getInstance().globalContainerStackChanged.connect(self._update) Application.getInstance().globalContainerStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update) Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update) Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update) Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
@ -54,16 +58,10 @@ class ProfilesModel(InstanceContainersModel):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None: if global_container_stack is None:
return {}, {} return {}, {}
global_stack_definition = global_container_stack.getBottom() global_stack_definition = global_container_stack.definition
# Get the list of extruders and place the selected extruder at the front of the list. # Get the list of extruders and place the selected extruder at the front of the list.
extruder_manager = ExtruderManager.getInstance() extruder_stacks = self._getOrderedExtruderStacksList()
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = extruder_manager.getActiveExtruderStacks()
materials = [global_container_stack.material]
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
extruder_stacks = [active_extruder] + extruder_stacks
materials = [extruder.material for extruder in extruder_stacks] materials = [extruder.material for extruder in extruder_stacks]
# Fetch the list of usable qualities across all extruders. # Fetch the list of usable qualities across all extruders.
@ -83,39 +81,27 @@ class ProfilesModel(InstanceContainersModel):
if quality.getMetaDataEntry("quality_type") not in quality_type_set: if quality.getMetaDataEntry("quality_type") not in quality_type_set:
result.append(quality) result.append(quality)
# if still profiles are found, add a single empty_quality ("Not supported") instance to the drop down list
if len(result) == 0:
# If not qualities are found we dynamically create a not supported container for this machine + material combination
not_supported_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
result.append(not_supported_container)
return {item.getId():item for item in result}, {} return {item.getId():item for item in result}, {}
## Re-computes the items in this model, and adds the layer height role. ## Re-computes the items in this model, and adds the layer height role.
def _recomputeItems(self): def _recomputeItems(self):
# Some globals that we can re-use. # Some globals that we can re-use.
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None: if global_container_stack is None:
return return
# Detecting if the machine has multiple extrusion extruder_stacks = self._getOrderedExtruderStacksList()
multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 container_registry = ContainerRegistry.getInstance()
# Get the list of extruders and place the selected extruder at the front of the list.
extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = extruder_manager.getActiveExtruderStacks()
if multiple_extrusion:
# Place the active extruder at the front of the list.
# This is a workaround checking if there is an active_extruder or not before moving it to the front of the list.
# Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some
# cases the active_extruder is still None.
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
new_extruder_stacks = []
if active_extruder is not None:
new_extruder_stacks = [active_extruder]
extruder_stacks = new_extruder_stacks + extruder_stacks
# Get a list of usable/available qualities for this machine and material # Get a list of usable/available qualities for this machine and material
qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
extruder_stacks)
container_registry = ContainerRegistry.getInstance()
machine_manager = Application.getInstance().getMachineManager()
unit = global_container_stack.getBottom().getProperty("layer_height", "unit") unit = global_container_stack.getBottom().getProperty("layer_height", "unit")
if not unit: if not unit:
@ -160,13 +146,23 @@ class ProfilesModel(InstanceContainersModel):
# Now all the containers are set # Now all the containers are set
for item in containers: for item in containers:
profile = container_registry.findContainers(id = item["id"]) profile = container_registry.findContainers(id = item["id"])
# When for some reason there is no profile container in the registry
if not profile: if not profile:
self._setItemLayerHeight(item, "", unit) self._setItemLayerHeight(item, "", "")
item["available"] = False item["available"] = False
yield item yield item
continue continue
profile = profile[0] profile = profile[0]
# When there is a profile but it's an empty quality should. It's shown in the list (they are "Not Supported" profiles)
if profile.getId() == "empty_quality":
self._setItemLayerHeight(item, "", "")
item["available"] = True
yield item
continue
item["available"] = profile in qualities item["available"] = profile in qualities
# Easy case: This profile defines its own layer height. # Easy case: This profile defines its own layer height.
@ -175,6 +171,8 @@ class ProfilesModel(InstanceContainersModel):
yield item yield item
continue continue
machine_manager = Application.getInstance().getMachineManager()
# Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile. # Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile.
quality_type = profile.getMetaDataEntry("quality_type", None) quality_type = profile.getMetaDataEntry("quality_type", None)
if quality_type: if quality_type:
@ -183,9 +181,11 @@ class ProfilesModel(InstanceContainersModel):
if quality_result["stack"] is global_container_stack: if quality_result["stack"] is global_container_stack:
quality = quality_result["quality"] quality = quality_result["quality"]
break break
else: #No global container stack in the results: else:
# No global container stack in the results:
if quality_results: if quality_results:
quality = quality_results[0]["quality"] #Take any of the extruders. # Take any of the extruders.
quality = quality_results[0]["quality"]
else: else:
quality = None quality = None
if quality and quality.hasProperty("layer_height", "value"): if quality and quality.hasProperty("layer_height", "value"):
@ -202,6 +202,20 @@ class ProfilesModel(InstanceContainersModel):
self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material. self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material.
yield item yield item
def _setItemLayerHeight(self, item, value, unit): ## Get a list of extruder stacks with the active extruder at the front of the list.
@staticmethod
def _getOrderedExtruderStacksList() -> List["ExtruderStack"]:
extruder_manager = ExtruderManager.getInstance()
extruder_stacks = extruder_manager.getActiveExtruderStacks()
active_extruder = extruder_manager.getActiveExtruderStack()
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
extruder_stacks = [active_extruder] + extruder_stacks
return extruder_stacks
@staticmethod
def _setItemLayerHeight(item, value, unit):
item["layer_height"] = str(value) + unit item["layer_height"] = str(value) + unit
item["layer_height_without_unit"] = str(value) item["layer_height_without_unit"] = str(value)

View file

@ -22,49 +22,22 @@ class QualityAndUserProfilesModel(ProfilesModel):
# Fetch the list of quality changes. # Fetch the list of quality changes.
quality_manager = QualityManager.getInstance() quality_manager = QualityManager.getInstance()
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition) quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
# Detecting if the machine has multiple extrusion
multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
# Get the list of extruders
extruder_manager = ExtruderManager.getInstance() extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack() active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = extruder_manager.getActiveExtruderStacks() extruder_stacks = self._getOrderedExtruderStacksList()
if multiple_extrusion:
# Place the active extruder at the front of the list.
# This is a workaround checking if there is an active_extruder or not before moving it to the front of the list.
# Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some
# cases the active_extruder is still None.
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
new_extruder_stacks = []
if active_extruder is not None:
new_extruder_stacks = [active_extruder]
else:
# if there is no active extruder, use the first one in the active extruder stacks
active_extruder = extruder_stacks[0]
extruder_stacks = new_extruder_stacks + extruder_stacks
# Fetch the list of useable qualities across all extruders. # Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list. # The actual list of quality profiles come from the first extruder in the extruder list.
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
extruder_stacks)
result = {quality.getId():quality for quality in quality_list}
# Filter the quality_change by the list of available quality_types # Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
if multiple_extrusion: qc.getMetaDataEntry("quality_type") in quality_type_set and
# If the printer has multiple extruders then quality changes related to the current extruder are kept
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())} qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
else: return filtered_quality_changes, {}
# If not, the quality changes of the global stack are selected
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is None}
result.update(filtered_quality_changes)
return result, {}

View file

@ -223,7 +223,6 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
if self._extruder_id == "" and settable_per_extruder: if self._extruder_id == "" and settable_per_extruder:
continue continue
label = definition.label label = definition.label
if self._i18n_catalog: if self._i18n_catalog:
label = self._i18n_catalog.i18nc(definition.key + " label", label) label = self._i18n_catalog.i18nc(definition.key + " label", label)

View file

@ -47,21 +47,20 @@ class SettingInheritanceManager(QObject):
@pyqtSlot(str, str, result = "QStringList") @pyqtSlot(str, str, result = "QStringList")
def getOverridesForExtruder(self, key, extruder_index): def getOverridesForExtruder(self, key, extruder_index):
multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1 result = []
if not multi_extrusion:
return self._settings_with_inheritance_warning extruder_stack = ExtruderManager.getInstance().getExtruderStack(extruder_index)
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) if not extruder_stack:
if not extruder:
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index) Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
return [] return result
definitions = self._global_container_stack.definition.findDefinitions(key = key) definitions = self._global_container_stack.definition.findDefinitions(key = key)
if not definitions: if not definitions:
Logger.log("w", "Could not find definition for key [%s] (2)", key) Logger.log("w", "Could not find definition for key [%s] (2)", key)
return [] return result
result = []
for key in definitions[0].getAllKeys(): for key in definitions[0].getAllKeys():
if self._settingIsOverwritingInheritance(key, extruder): if self._settingIsOverwritingInheritance(key, extruder_stack):
result.append(key) result.append(key)
return result return result
@ -78,8 +77,8 @@ class SettingInheritanceManager(QObject):
def _onActiveExtruderChanged(self): def _onActiveExtruderChanged(self):
new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack() new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack()
if not new_active_stack: # if not new_active_stack:
new_active_stack = self._global_container_stack # new_active_stack = self._global_container_stack
if new_active_stack != self._active_container_stack: # Check if changed if new_active_stack != self._active_container_stack: # Check if changed
if self._active_container_stack: # Disconnect signal from old container (if any) if self._active_container_stack: # Disconnect signal from old container (if any)

View file

@ -27,11 +27,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
self._stack = PerObjectContainerStack(stack_id = id(self)) self._stack = PerObjectContainerStack(stack_id = id(self))
self._stack.setDirty(False) # This stack does not need to be saved. self._stack.setDirty(False) # This stack does not need to be saved.
self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer")) self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer"))
if ExtruderManager.getInstance().extruderCount > 1:
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
else:
self._extruder_stack = None
self._stack.propertyChanged.connect(self._onSettingChanged) self._stack.propertyChanged.connect(self._onSettingChanged)

View file

@ -12,11 +12,18 @@ class SimpleModeSettingsManager(QObject):
super().__init__(parent) super().__init__(parent)
self._machine_manager = Application.getInstance().getMachineManager() self._machine_manager = Application.getInstance().getMachineManager()
self._is_profile_customized = False self._is_profile_customized = False # True when default profile has user changes
self._is_profile_user_created = False # True when profile was custom created by user
self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized) self._machine_manager.activeStackValueChanged.connect(self._updateIsProfileCustomized)
self._machine_manager.activeQualityChanged.connect(self._updateIsProfileUserCreated)
# update on create as the activeQualityChanged signal is emitted before this manager is created when Cura starts
self._updateIsProfileCustomized()
self._updateIsProfileUserCreated()
isProfileCustomizedChanged = pyqtSignal() isProfileCustomizedChanged = pyqtSignal()
isProfileUserCreatedChanged = pyqtSignal()
@pyqtProperty(bool, notify = isProfileCustomizedChanged) @pyqtProperty(bool, notify = isProfileCustomizedChanged)
def isProfileCustomized(self): def isProfileCustomized(self):
@ -32,11 +39,13 @@ class SimpleModeSettingsManager(QObject):
# check user settings in the global stack # check user settings in the global stack
user_setting_keys.update(set(global_stack.userChanges.getAllKeys())) user_setting_keys.update(set(global_stack.userChanges.getAllKeys()))
# check user settings in the extruder stacks # check user settings in the extruder stacks
if global_stack.extruders: if global_stack.extruders:
for extruder_stack in global_stack.extruders.values(): for extruder_stack in global_stack.extruders.values():
user_setting_keys.update(set(extruder_stack.userChanges.getAllKeys())) user_setting_keys.update(set(extruder_stack.userChanges.getAllKeys()))
# remove settings that are visible in recommended (we don't show the reset button for those)
for skip_key in self.__ignored_custom_setting_keys: for skip_key in self.__ignored_custom_setting_keys:
if skip_key in user_setting_keys: if skip_key in user_setting_keys:
user_setting_keys.remove(skip_key) user_setting_keys.remove(skip_key)
@ -47,6 +56,33 @@ class SimpleModeSettingsManager(QObject):
self._is_profile_customized = has_customized_user_settings self._is_profile_customized = has_customized_user_settings
self.isProfileCustomizedChanged.emit() self.isProfileCustomizedChanged.emit()
@pyqtProperty(bool, notify = isProfileUserCreatedChanged)
def isProfileUserCreated(self):
return self._is_profile_user_created
def _updateIsProfileUserCreated(self):
quality_changes_keys = set()
if not self._machine_manager.activeMachine:
return False
global_stack = self._machine_manager.activeMachine
# check quality changes settings in the global stack
quality_changes_keys.update(set(global_stack.qualityChanges.getAllKeys()))
# check quality changes settings in the extruder stacks
if global_stack.extruders:
for extruder_stack in global_stack.extruders.values():
quality_changes_keys.update(set(extruder_stack.qualityChanges.getAllKeys()))
# check if the qualityChanges container is not empty (meaning it is a user created profile)
has_quality_changes = len(quality_changes_keys) > 0
if has_quality_changes != self._is_profile_user_created:
self._is_profile_user_created = has_quality_changes
self.isProfileUserCreatedChanged.emit()
# These are the settings included in the Simple ("Recommended") Mode, so only when the other settings have been # These are the settings included in the Simple ("Recommended") Mode, so only when the other settings have been
# changed, we consider it as a user customized profile in the Simple ("Recommended") Mode. # changed, we consider it as a user customized profile in the Simple ("Recommended") Mode.
__ignored_custom_setting_keys = ["support_enable", __ignored_custom_setting_keys = ["support_enable",

View file

@ -22,47 +22,24 @@ class UserProfilesModel(ProfilesModel):
# Fetch the list of quality changes. # Fetch the list of quality changes.
quality_manager = QualityManager.getInstance() quality_manager = QualityManager.getInstance()
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition) quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
# Detecting if the machine has multiple extrusion
multiple_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
# Get the list of extruders and place the selected extruder at the front of the list.
extruder_manager = ExtruderManager.getInstance() extruder_manager = ExtruderManager.getInstance()
active_extruder = extruder_manager.getActiveExtruderStack() active_extruder = extruder_manager.getActiveExtruderStack()
extruder_stacks = extruder_manager.getActiveExtruderStacks() extruder_stacks = self._getOrderedExtruderStacksList()
if multiple_extrusion:
# Place the active extruder at the front of the list.
# This is a workaround checking if there is an active_extruder or not before moving it to the front of the list.
# Actually, when a printer has multiple extruders, should exist always an active_extruder. However, in some
# cases the active_extruder is still None.
if active_extruder in extruder_stacks:
extruder_stacks.remove(active_extruder)
new_extruder_stacks = []
if active_extruder is not None:
new_extruder_stacks = [active_extruder]
else:
# if there is no active extruder, use the first one in the active extruder stacks
active_extruder = extruder_stacks[0]
extruder_stacks = new_extruder_stacks + extruder_stacks
# Fetch the list of useable qualities across all extruders. # Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list. # The actual list of quality profiles come from the first extruder in the extruder list.
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
extruder_stacks)
# Filter the quality_change by the list of available quality_types # Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
if multiple_extrusion: filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
# If the printer has multiple extruders then quality changes related to the current extruder are kept qc.getMetaDataEntry("quality_type") in quality_type_set and
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())} qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
else:
# If not, the quality changes of the global stack are selected
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is None}
return filtered_quality_changes, {} return filtered_quality_changes, {}

View file

@ -107,20 +107,13 @@ class ThreeMFReader(MeshReader):
um_node.addDecorator(SettingOverrideDecorator()) um_node.addDecorator(SettingOverrideDecorator())
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
# Ensure the correct next container for the SettingOverride decorator is set. # Ensure the correct next container for the SettingOverride decorator is set.
if global_container_stack: if global_container_stack:
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
# Ensure that all extruder data is reset
if not multi_extrusion:
default_stack_id = global_container_stack.getId()
else:
default_stack = ExtruderManager.getInstance().getExtruderStack(0) default_stack = ExtruderManager.getInstance().getExtruderStack(0)
if default_stack: if default_stack:
default_stack_id = default_stack.getId() um_node.callDecoration("setActiveExtruder", default_stack.getId())
else:
default_stack_id = global_container_stack.getId()
um_node.callDecoration("setActiveExtruder", default_stack_id)
# Get the definition & set it # Get the definition & set it
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())

View file

@ -13,6 +13,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.MimeTypeDatabase import MimeTypeDatabase from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.Job import Job from UM.Job import Job
from UM.Preferences import Preferences from UM.Preferences import Preferences
from UM.Util import parseBool
from .WorkspaceDialog import WorkspaceDialog from .WorkspaceDialog import WorkspaceDialog
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@ -21,6 +22,8 @@ from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.CuraContainerStack import _ContainerIndexes
from cura.QualityManager import QualityManager
from configparser import ConfigParser from configparser import ConfigParser
import zipfile import zipfile
@ -147,7 +150,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if not definitions: if not definitions:
definition_container = DefinitionContainer(container_id) definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8")) definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), file_name = each_definition_container_file)
definition_container = definition_container.getMetaData() definition_container = definition_container.getMetaData()
else: else:
@ -202,7 +205,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
instance_container = InstanceContainer(container_id) instance_container = InstanceContainer(container_id)
# Deserialize InstanceContainer by converting read data from bytes to string # Deserialize InstanceContainer by converting read data from bytes to string
instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8")) instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"),
file_name = each_instance_container_file)
instance_container_list.append(instance_container) instance_container_list.append(instance_container)
container_type = instance_container.getMetaDataEntry("type") container_type = instance_container.getMetaDataEntry("type")
@ -372,7 +376,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return WorkspaceReader.PreReadResult.accepted return WorkspaceReader.PreReadResult.accepted
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack. ## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
def _overrideExtruderStack(self, global_stack, extruder_file_content): def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file):
# Get extruder position first # Get extruder position first
extruder_config = configparser.ConfigParser() extruder_config = configparser.ConfigParser()
extruder_config.read_string(extruder_file_content) extruder_config.read_string(extruder_file_content)
@ -388,7 +392,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return None return None
# Override the given extruder stack # Override the given extruder stack
extruder_stack.deserialize(extruder_file_content) extruder_stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
# return the new ExtruderStack # return the new ExtruderStack
return extruder_stack return extruder_stack
@ -478,7 +482,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id) definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
if not definitions: if not definitions:
definition_container = DefinitionContainer(container_id) definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8")) definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
file_name = definition_container_file)
self._container_registry.addContainer(definition_container) self._container_registry.addContainer(definition_container)
Job.yieldThread() Job.yieldThread()
@ -496,18 +501,21 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if not materials: if not materials:
material_container = xml_material_profile(container_id) material_container = xml_material_profile(container_id)
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
file_name = material_container_file)
containers_to_add.append(material_container) containers_to_add.append(material_container)
else: else:
material_container = materials[0] material_container = materials[0]
if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only. if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only.
if self._resolve_strategies["material"] == "override": if self._resolve_strategies["material"] == "override":
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
file_name = material_container_file)
elif self._resolve_strategies["material"] == "new": elif self._resolve_strategies["material"] == "new":
# Note that we *must* deserialize it with a new ID, as multiple containers will be # Note that we *must* deserialize it with a new ID, as multiple containers will be
# auto created & added. # auto created & added.
material_container = xml_material_profile(self.getNewId(container_id)) material_container = xml_material_profile(self.getNewId(container_id))
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8")) material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
file_name = material_container_file)
containers_to_add.append(material_container) containers_to_add.append(material_container)
material_containers.append(material_container) material_containers.append(material_container)
@ -534,7 +542,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
instance_container = InstanceContainer(container_id) instance_container = InstanceContainer(container_id)
# Deserialize InstanceContainer by converting read data from bytes to string # Deserialize InstanceContainer by converting read data from bytes to string
instance_container.deserialize(serialized) instance_container.deserialize(serialized, file_name = instance_container_file)
container_type = instance_container.getMetaDataEntry("type") container_type = instance_container.getMetaDataEntry("type")
Job.yieldThread() Job.yieldThread()
@ -556,7 +564,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
else: else:
if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None: if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
instance_container = user_containers[0] instance_container = user_containers[0]
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
file_name = instance_container_file)
instance_container.setDirty(True) instance_container.setDirty(True)
elif self._resolve_strategies["machine"] == "new": elif self._resolve_strategies["machine"] == "new":
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match. # The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
@ -589,7 +598,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# selected strategy. # selected strategy.
if self._resolve_strategies[container_type] == "override": if self._resolve_strategies[container_type] == "override":
instance_container = changes_containers[0] instance_container = changes_containers[0]
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8")) instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
file_name = instance_container_file)
instance_container.setDirty(True) instance_container.setDirty(True)
elif self._resolve_strategies[container_type] == "new": elif self._resolve_strategies[container_type] == "new":
@ -638,9 +648,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Get the stack(s) saved in the workspace. # Get the stack(s) saved in the workspace.
Logger.log("d", "Workspace loading is checking stacks containers...") Logger.log("d", "Workspace loading is checking stacks containers...")
# --
# load global stack file # load global stack file
try: try:
stack = None
if self._resolve_strategies["machine"] == "override": if self._resolve_strategies["machine"] == "override":
container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
stack = container_stacks[0] stack = container_stacks[0]
@ -649,7 +660,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# There is a machine, check if it has authentication data. If so, keep that data. # There is a machine, check if it has authentication data. If so, keep that data.
network_authentication_id = stack.getMetaDataEntry("network_authentication_id") network_authentication_id = stack.getMetaDataEntry("network_authentication_id")
network_authentication_key = stack.getMetaDataEntry("network_authentication_key") network_authentication_key = stack.getMetaDataEntry("network_authentication_key")
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"), file_name = global_stack_file)
if network_authentication_id: if network_authentication_id:
stack.addMetaDataEntry("network_authentication_id", network_authentication_id) stack.addMetaDataEntry("network_authentication_id", network_authentication_id)
if network_authentication_key: if network_authentication_key:
@ -659,7 +670,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# create a new global stack # create a new global stack
stack = GlobalStack(global_stack_id_new) stack = GlobalStack(global_stack_id_new)
# Deserialize stack by converting read data from bytes to string # Deserialize stack by converting read data from bytes to string
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8")) stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"),
file_name = global_stack_file)
# Ensure a unique ID and name # Ensure a unique ID and name
stack._id = global_stack_id_new stack._id = global_stack_id_new
@ -676,12 +688,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._container_registry.addContainer(stack) self._container_registry.addContainer(stack)
containers_added.append(stack) containers_added.append(stack)
else: else:
Logger.log("e", "Resolve strategy of %s for machine is not supported", Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
self._resolve_strategies["machine"])
# Create a new definition_changes container if it was empty # Create a new definition_changes container if it was empty
if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings")) stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
global_stack = stack global_stack = stack
Job.yieldThread() Job.yieldThread()
except: except:
@ -691,16 +702,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._container_registry.removeContainer(container.getId()) self._container_registry.removeContainer(container.getId())
return return
#
# Use the number of extruders from the global stack instead of the number of extruder stacks this project file
# contains. The Custom FDM Printer can have multiple extruders, but the actual number of extruders in used is
# defined in the global stack.
# Because for single-extrusion machines, there won't be an extruder stack, so relying on the the extruder count
# in the global stack can avoid problems in those cases.
#
extruder_count_from_global_stack = global_stack.getProperty("machine_extruder_count", "value")
# --
# load extruder stack files # load extruder stack files
try: try:
for extruder_stack_file in extruder_stack_files: for extruder_stack_file in extruder_stack_files:
@ -708,9 +709,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8") extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
if self._resolve_strategies["machine"] == "override": if self._resolve_strategies["machine"] == "override":
if global_stack.getProperty("machine_extruder_count", "value") > 1:
# deserialize new extruder stack over the current ones (if any) # deserialize new extruder stack over the current ones (if any)
stack = self._overrideExtruderStack(global_stack, extruder_file_content) stack = self._overrideExtruderStack(global_stack, extruder_file_content, extruder_stack_file)
if stack is None: if stack is None:
continue continue
@ -730,7 +730,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_config.write(tmp_string_io) extruder_config.write(tmp_string_io)
extruder_file_content = tmp_string_io.getvalue() extruder_file_content = tmp_string_io.getvalue()
stack.deserialize(extruder_file_content) stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
# Ensure a unique ID and name # Ensure a unique ID and name
stack._id = new_id stack._id = new_id
@ -743,9 +743,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Create a new definition_changes container if it was empty # Create a new definition_changes container if it was empty
if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings")) stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
if global_stack.getProperty("machine_extruder_count", "value") > 1:
if stack.getMetaDataEntry("type") == "extruder_train":
extruder_stacks.append(stack) extruder_stacks.append(stack)
# If not extruder stacks were saved in the project file (pre 3.1) create one manually
# We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this
if not extruder_stacks:
stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder")
if stack:
if self._resolve_strategies["machine"] == "override":
# in case the extruder is newly created (for a single-extrusion machine), we need to override
# the existing extruder stack.
existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")]
for idx in range(len(_ContainerIndexes.IndexTypeMap)):
existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True)
extruder_stacks.append(existing_extruder_stack)
else:
extruder_stacks.append(stack)
except: except:
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
# Something went really wrong. Try to remove any data that we added. # Something went really wrong. Try to remove any data that we added.
@ -753,13 +770,117 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._container_registry.removeContainer(container.getId()) self._container_registry.removeContainer(container.getId())
return return
# Check quality profiles to make sure that if one stack has the "not supported" quality profile,
# all others should have the same.
# #
# This block code tries to fix the following problems in Cura 3.0 and earlier:
# 1. The upgrade script can rename all "Not Supported" quality profiles to "empty_quality", but it cannot fix
# the problem that the global stack the extruder stacks may have different quality profiles. The code
# below loops over all stacks and make sure that if there is one stack with "Not Supported" profile, the
# rest should also use the "Not Supported" profile.
# 2. In earlier versions (at least 2.7 and 3.0), a wrong quality profile could be assigned to a stack. For
# example, a UM3 can have a BB 0.8 variant with "aa04_pla_fast" quality profile enabled. To fix this,
# in the code below we also check the actual available quality profiles for the machine.
#
has_not_supported = False
for stack in [global_stack] + extruder_stacks:
if stack.quality.getId() in ("empty", "empty_quality"):
has_not_supported = True
break
available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack,
extruder_stacks)
if not has_not_supported:
has_not_supported = not available_quality
quality_has_been_changed = False
if has_not_supported:
empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
for stack in [global_stack] + extruder_stacks:
stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container)
empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
for stack in [global_stack] + extruder_stacks:
stack.replaceContainer(_ContainerIndexes.QualityChanges, empty_quality_changes_container)
quality_has_been_changed = True
else:
empty_quality_changes_container = self._container_registry.findInstanceContainers(id="empty_quality_changes")[0]
# The machine in the project has non-empty quality and there are usable qualities for this machine.
# We need to check if the current quality_type is still usable for this machine, if not, then the quality
# will be reset to the "preferred quality" if present, otherwise "normal".
available_quality_types = [q.getMetaDataEntry("quality_type") for q in available_quality]
if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types:
# We are here because the quality_type specified in the project is not supported any more,
# so we need to switch it to the "preferred quality" if present, otherwise "normal".
quality_has_been_changed = True
# find the preferred quality
preferred_quality_id = global_stack.getMetaDataEntry("preferred_quality", None)
if preferred_quality_id is not None:
definition_id = global_stack.definition.getId()
definition_id = global_stack.definition.getMetaDataEntry("quality_definition", definition_id)
if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
definition_id = "fdmprinter"
containers = self._container_registry.findInstanceContainers(id = preferred_quality_id,
type = "quality",
definition = definition_id)
containers = [c for c in containers if not c.getMetaDataEntry("material", "")]
if containers:
global_stack.quality = containers[0]
global_stack.qualityChanges = empty_quality_changes_container
# also find the quality containers for the extruders
for extruder_stack in extruder_stacks:
search_criteria = {"id": preferred_quality_id,
"type": "quality",
"definition": definition_id}
if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
search_criteria["material"] = extruder_stack.material.getId()
containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers:
extruder_stack.quality = containers[0]
extruder_stack.qualityChanges = empty_quality_changes_container
else:
Logger.log("e", "Cannot find preferred quality for extruder [%s].", extruder_stack.getId())
else:
# we cannot find the preferred quality. THIS SHOULD NOT HAPPEN
Logger.log("e", "Cannot find the preferred quality for machine [%s]", global_stack.getId())
else:
# The quality_type specified in the project file is usable, but the quality container itself may not
# be correct. For example, for UM2, the quality container can be "draft" while it should be "um2_draft"
# instead.
# In this code branch, we try to fix those incorrect quality containers.
#
# ***IMPORTANT***: We only do this fix for single-extrusion machines.
# We will first find the correct quality profile for the extruder, then apply the same
# quality profile for the global stack.
#
if len(extruder_stacks) == 1:
extruder_stack = extruder_stacks[0]
search_criteria = {"type": "quality",
"quality_type": global_stack.quality.getMetaDataEntry("quality_type")}
search_criteria["definition"] = global_stack.definition.getId()
if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
search_criteria["definition"] = "fdmprinter"
if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
search_criteria["material"] = extruder_stack.material.getId()
containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers:
new_quality_container = containers[0]
extruder_stack.quality = new_quality_container
global_stack.quality = new_quality_container
# Replacing the old containers if resolve is "new". # Replacing the old containers if resolve is "new".
# When resolve is "new", some containers will get renamed, so all the other containers that reference to those # When resolve is "new", some containers will get renamed, so all the other containers that reference to those
# MUST get updated too. # MUST get updated too.
# #
if self._resolve_strategies["machine"] == "new": if self._resolve_strategies["machine"] == "new":
# A new machine was made, but it was serialized with the wrong user container. Fix that now. # A new machine was made, but it was serialized with the wrong user container. Fix that now.
for container in user_instance_containers: for container in user_instance_containers:
# replacing the container ID for user instance containers for the extruders # replacing the container ID for user instance containers for the extruders
@ -777,7 +898,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
global_stack.userChanges = container global_stack.userChanges = container
continue continue
for changes_container_type in ("quality_changes", "definition_changes"): changes_container_types = ("quality_changes", "definition_changes")
if quality_has_been_changed:
# DO NOT replace quality_changes if the current quality_type is not supported
changes_container_types = ("definition_changes",)
for changes_container_type in changes_container_types:
if self._resolve_strategies[changes_container_type] == "new": if self._resolve_strategies[changes_container_type] == "new":
# Quality changes needs to get a new ID, added to registry and to the right stacks # Quality changes needs to get a new ID, added to registry and to the right stacks
for each_changes_container in quality_and_definition_changes_instance_containers: for each_changes_container in quality_and_definition_changes_instance_containers:
@ -877,7 +1002,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
base_file_name = os.path.basename(file_name) base_file_name = os.path.basename(file_name)
if base_file_name.endswith(".curaproject.3mf"): if base_file_name.endswith(".curaproject.3mf"):
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")] base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
Application.getInstance().projectFileLoaded.emit(base_file_name) self.setWorkspaceName(base_file_name)
return nodes return nodes
## HACK: Replaces the material container in the given stack with a newly created material container. ## HACK: Replaces the material container in the given stack with a newly created material container.

View file

@ -256,14 +256,8 @@ class WorkspaceDialog(QObject):
return self._result return self._result
def _createViewFromQML(self): def _createViewFromQML(self):
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url)) path = os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url)
self._component = QQmlComponent(Application.getInstance()._engine, path) self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
self._context.setContextProperty("manager", self)
self._view = self._component.create(self._context)
if self._view is None:
Logger.log("c", "QQmlComponent status %s", self._component.status())
Logger.log("c", "QQmlComponent error string %s", self._component.errorString())
def show(self): def show(self):
# Emit signal so the right thread actually shows the view. # Emit signal so the right thread actually shows the view.

View file

@ -87,7 +87,7 @@ class ThreeMFWriter(MeshWriter):
if stack is not None: if stack is not None:
changed_setting_keys = set(stack.getTop().getAllKeys()) changed_setting_keys = set(stack.getTop().getAllKeys())
# Ensure that we save the extruder used for this object. # Ensure that we save the extruder used for this object in a multi-extrusion setup
if stack.getProperty("machine_extruder_count", "value") > 1: if stack.getProperty("machine_extruder_count", "value") > 1:
changed_setting_keys.add("extruder_nr") changed_setting_keys.add("extruder_nr")

View file

@ -8,7 +8,6 @@ from UM.Application import Application
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Version import Version from UM.Version import Version
from PyQt5.QtQuick import QQuickView
from PyQt5.QtQml import QQmlComponent, QQmlContext from PyQt5.QtQml import QQmlComponent, QQmlContext
from PyQt5.QtCore import QUrl, pyqtSlot, QObject from PyQt5.QtCore import QUrl, pyqtSlot, QObject
@ -107,9 +106,5 @@ class ChangeLog(Extension, QObject,):
self._changelog_window.hide() self._changelog_window.hide()
def createChangelogWindow(self): def createChangelogWindow(self):
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")) path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")
self._changelog_window = Application.getInstance().createQmlComponent(path, {"manager": self})
component = QQmlComponent(Application.getInstance()._engine, path)
self._changelog_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._changelog_context.setContextProperty("manager", self)
self._changelog_window = component.create(self._changelog_context)

View file

@ -1,3 +1,13 @@
[3.0.4]
*Bug fixes
- Fixed OpenGL issue that prevents Cura from starting.
*License agreement on the first startup has been added
[3.0.3]
*Bug fixes
- Add missing libraries for the MakePrintable plugin.
[3.0.0] [3.0.0]
*Faster start-up *Faster start-up
Start-up speed has been cut in half compared to the previous version. Start-up speed has been cut in half compared to the previous version.

View file

@ -61,6 +61,8 @@ message Polygon {
Type type = 1; // Type of move Type type = 1; // Type of move
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used) bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
float line_width = 3; // The width of the line being laid down float line_width = 3; // The width of the line being laid down
float line_thickness = 4; // The thickness of the line being laid down
float line_feedrate = 5; // The feedrate of the line being laid down
} }
message LayerOptimized { message LayerOptimized {
@ -82,6 +84,8 @@ message PathSegment {
bytes points = 3; // The points defining the line segments, bytes of float[2/3] array of length N+1 bytes points = 3; // The points defining the line segments, bytes of float[2/3] array of length N+1
bytes line_type = 4; // Type of line segment as an unsigned char array of length 1 or N, where N is the number of line segments in this path bytes line_type = 4; // Type of line segment as an unsigned char array of length 1 or N, where N is the number of line segments in this path
bytes line_width = 5; // The widths of the line segments as bytes of a float array of length 1 or N bytes line_width = 5; // The widths of the line segments as bytes of a float array of length 1 or N
bytes line_thickness = 6; // The thickness of the line segments as bytes of a float array of length 1 or N
bytes line_feedrate = 7; // The feedrate of the line segments as bytes of a float array of length 1 or N
} }

View file

@ -293,7 +293,7 @@ class CuraEngineBackend(QObject, Backend):
error_labels.add(definitions[0].label) error_labels.add(definitions[0].label)
error_labels = ", ".join(error_labels) error_labels = ", ".join(error_labels)
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels)), self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(error_labels),
title = catalog.i18nc("@info:title", "Unable to slice")) title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show() self._error_message.show()
self.backendStateChange.emit(BackendState.Error) self.backendStateChange.emit(BackendState.Error)
@ -301,6 +301,26 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.NotStarted) self.backendStateChange.emit(BackendState.NotStarted)
return return
elif job.getResult() == StartSliceJob.StartJobResult.ObjectSettingError:
errors = {}
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
stack = node.callDecoration("getStack")
if not stack:
continue
for key in stack.getErrorKeys():
definition = 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))
continue
definition = definition[0]
errors[key] = definition.label
error_labels = ", ".join(errors.values())
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = error_labels),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.backendStateChange.emit(BackendState.Error)
return
if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError: if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError:
if Application.getInstance().platformActivity: if Application.getInstance().platformActivity:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."), self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
@ -588,7 +608,7 @@ class CuraEngineBackend(QObject, Backend):
def _onActiveViewChanged(self): def _onActiveViewChanged(self):
if Application.getInstance().getController().getActiveView(): if Application.getInstance().getController().getActiveView():
view = Application.getInstance().getController().getActiveView() view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet. if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True self._layer_view_active = True
# There is data and we're not slicing at the moment # There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.

View file

@ -61,7 +61,7 @@ class ProcessSlicedLayersJob(Job):
def run(self): def run(self):
start_time = time() start_time = time()
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
self._progress_message.show() self._progress_message.show()
Job.yieldThread() Job.yieldThread()
if self._abort_requested: if self._abort_requested:
@ -95,20 +95,27 @@ class ProcessSlicedLayersJob(Job):
# Find the minimum layer number # Find the minimum layer number
# When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we # When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
# instead simply offset all other layers so the lowest layer is always 0. # instead simply offset all other layers so the lowest layer is always 0. It could happens that
# the first raft layer has value -8 but there are just 4 raft (negative) layers.
min_layer_number = 0 min_layer_number = 0
negative_layers = 0
for layer in self._layers: for layer in self._layers:
if layer.id < min_layer_number: if layer.id < min_layer_number:
min_layer_number = layer.id min_layer_number = layer.id
if layer.id < 0:
negative_layers += 1
current_layer = 0 current_layer = 0
for layer in self._layers: for layer in self._layers:
abs_layer_number = layer.id + abs(min_layer_number) # Negative layers are offset by the minimum layer number, but the positive layers are just
# offset by the number of negative layers so there is no layer gap between raft and model
abs_layer_number = layer.id + abs(min_layer_number) if layer.id < 0 else layer.id + negative_layers
layer_data.addLayer(abs_layer_number) layer_data.addLayer(abs_layer_number)
this_layer = layer_data.getLayer(abs_layer_number) this_layer = layer_data.getLayer(abs_layer_number)
layer_data.setLayerHeight(abs_layer_number, layer.height) layer_data.setLayerHeight(abs_layer_number, layer.height)
layer_data.setLayerThickness(abs_layer_number, layer.thickness)
for p in range(layer.repeatedMessageCount("path_segment")): for p in range(layer.repeatedMessageCount("path_segment")):
polygon = layer.getRepeatedMessage("path_segment", p) polygon = layer.getRepeatedMessage("path_segment", p)
@ -127,10 +134,11 @@ class ProcessSlicedLayersJob(Job):
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
# In the future, line_thicknesses should be given by CuraEngine as well. line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array
# Currently the infill layer thickness also translates to line width line_thicknesses = line_thicknesses.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4")
line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype="f4") # Convert bytearray to numpy array
line_feedrates = line_feedrates.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
# Create a new 3D-array, copy the 2D points over and insert the right height. # Create a new 3D-array, copy the 2D points over and insert the right height.
# This uses manual array creation + copy rather than numpy.insert since this is # This uses manual array creation + copy rather than numpy.insert since this is
@ -145,7 +153,7 @@ class ProcessSlicedLayersJob(Job):
new_points[:, 1] = points[:, 2] new_points[:, 1] = points[:, 2]
new_points[:, 2] = -points[:, 1] new_points[:, 2] = -points[:, 1]
this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses) this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses, line_feedrates)
this_poly.buildCache() this_poly.buildCache()
this_layer.polygons.append(this_poly) this_layer.polygons.append(this_poly)
@ -219,7 +227,7 @@ class ProcessSlicedLayersJob(Job):
self._progress_message.setProgress(100) self._progress_message.setProgress(100)
view = Application.getInstance().getController().getActiveView() view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "LayerView": if view.getPluginId() == "SimulationView":
view.resetLayerData() view.resetLayerData()
if self._progress_message: if self._progress_message:
@ -232,7 +240,7 @@ class ProcessSlicedLayersJob(Job):
def _onActiveViewChanged(self): def _onActiveViewChanged(self):
if self.isRunning(): if self.isRunning():
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView": if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
if not self._progress_message: if not self._progress_message:
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information")) self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information"))
if self._progress_message.getProgress() != 100: if self._progress_message.getProgress() != 100:
@ -240,3 +248,4 @@ class ProcessSlicedLayersJob(Job):
else: else:
if self._progress_message: if self._progress_message:
self._progress_message.hide() self._progress_message.hide()

View file

@ -26,6 +26,7 @@ class StartJobResult(IntEnum):
NothingToSlice = 4 NothingToSlice = 4
MaterialIncompatible = 5 MaterialIncompatible = 5
BuildPlateError = 6 BuildPlateError = 6
ObjectSettingError = 7 #When an error occurs in per-object settings.
## Formatter class that handles token expansion in start/end gcod ## Formatter class that handles token expansion in start/end gcod
@ -105,7 +106,7 @@ class StartSliceJob(Job):
continue continue
if self._checkStackForErrors(node.callDecoration("getStack")): if self._checkStackForErrors(node.callDecoration("getStack")):
self.setResult(StartJobResult.SettingError) self.setResult(StartJobResult.ObjectSettingError)
return return
with self._scene.getSceneLock(): with self._scene.getSceneLock():
@ -158,13 +159,9 @@ class StartSliceJob(Job):
self._buildGlobalSettingsMessage(stack) self._buildGlobalSettingsMessage(stack)
self._buildGlobalInheritsStackMessage(stack) self._buildGlobalInheritsStackMessage(stack)
# Only add extruder stacks if there are multiple extruders # Build messages for extruder stacks
# Single extruder machines only use the global stack to store setting values
if stack.getProperty("machine_extruder_count", "value") > 1:
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack) self._buildExtruderMessage(extruder_stack)
else:
self._buildExtruderMessageFromGlobalStack(stack)
for group in object_groups: for group in object_groups:
group_message = self._slice_message.addRepeatedMessage("object_lists") group_message = self._slice_message.addRepeatedMessage("object_lists")
@ -250,19 +247,6 @@ class StartSliceJob(Job):
setting.value = str(stack.getProperty(key, "value")).encode("utf-8") setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
Job.yieldThread() Job.yieldThread()
## Create extruder message from global stack
def _buildExtruderMessageFromGlobalStack(self, stack):
message = self._slice_message.addRepeatedMessage("extruders")
for key in stack.getAllKeys():
# Do not send settings that are not settable_per_extruder.
if not stack.getProperty(key, "settable_per_extruder"):
continue
setting = message.getMessage("settings").addRepeatedMessage("settings")
setting.name = key
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
Job.yieldThread()
## Sends all global settings to the engine. ## Sends all global settings to the engine.
# #
# The settings are taken from the global stack. This does not include any # The settings are taken from the global stack. This does not include any

View file

@ -1,6 +1,9 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from UM.Extension import Extension from UM.Extension import Extension
from UM.Preferences import Preferences from UM.Preferences import Preferences
from UM.Logger import Logger from UM.Logger import Logger
@ -32,6 +35,17 @@ class FirmwareUpdateChecker(Extension):
if Preferences.getInstance().getValue("info/automatic_update_check"): if Preferences.getInstance().getValue("info/automatic_update_check"):
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
self._download_url = None
## Callback for the message that is spawned when there is a new version.
def _onActionTriggered(self, message, action):
if action == "download":
if self._download_url is not None:
QDesktopServices.openUrl(QUrl(self._download_url))
def _onSetDownloadUrl(self, download_url):
self._download_url = download_url
def _onContainerAdded(self, container): def _onContainerAdded(self, container):
# Only take care when a new GlobalStack was added # Only take care when a new GlobalStack was added
if isinstance(container, GlobalStack): if isinstance(container, GlobalStack):
@ -45,5 +59,7 @@ class FirmwareUpdateChecker(Extension):
# \param silent type(boolean) Suppresses messages other than "new version found" messages. # \param silent type(boolean) Suppresses messages other than "new version found" messages.
# This is used when checking for a new firmware version at startup. # This is used when checking for a new firmware version at startup.
def checkFirmwareVersion(self, container = None, silent = False): def checkFirmwareVersion(self, container = None, silent = False):
job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL) job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL,
callback = self._onActionTriggered,
set_download_url_callback = self._onSetDownloadUrl)
job.start() job.start()

View file

@ -10,30 +10,21 @@ from UM.Job import Job
import urllib.request import urllib.request
import codecs import codecs
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
## This job checks if there is an update available on the provided URL. ## This job checks if there is an update available on the provided URL.
class FirmwareUpdateCheckerJob(Job): class FirmwareUpdateCheckerJob(Job):
def __init__(self, container = None, silent = False, url = None): def __init__(self, container = None, silent = False, url = None, callback = None, set_download_url_callback = None):
super().__init__() super().__init__()
self._container = container self._container = container
self.silent = silent self.silent = silent
self._url = url self._url = url
self._download_url = None # If an update was found, the download_url will be set to the location of the new version. self._callback = callback
self._set_download_url_callback = set_download_url_callback
## Callback for the message that is spawned when there is a new version.
def actionTriggered(self, message, action):
if action == "download":
if self._download_url is not None:
QDesktopServices.openUrl(QUrl(self._download_url))
def run(self): def run(self):
self._download_url = None # Reset download ur.
if not self._url: if not self._url:
Logger.log("e", "Can not check for a new release. URL not set!") Logger.log("e", "Can not check for a new release. URL not set!")
return return
@ -70,13 +61,14 @@ class FirmwareUpdateCheckerJob(Job):
# notify the user when no new firmware version is available. # notify the user when no new firmware version is available.
if (checked_version != "") and (checked_version != current_version): if (checked_version != "") and (checked_version != current_version):
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE") Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
message = Message(i18n_catalog.i18nc("@info Don't translate {machine_name}, since it gets replaced by a printer name!", "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB.").format(machine_name = machine_name), message = Message(i18n_catalog.i18nc("@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(machine_name = machine_name),
title = i18n_catalog.i18nc("@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name) title = i18n_catalog.i18nc("@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name)
message.addAction("download", i18n_catalog.i18nc("@action:button", "Download"), "[no_icon]", "[no_description]") message.addAction("download", i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]")
# If we do this in a cool way, the download url should be available in the JSON file # If we do this in a cool way, the download url should be available in the JSON file
self._download_url = "https://ultimaker.com/en/resources/20500-upgrade-firmware" if self._set_download_url_callback:
message.actionTriggered.connect(self.actionTriggered) self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
message.actionTriggered.connect(self._callback)
message.show() message.show()
except Exception as e: except Exception as e:

View file

@ -1,4 +1,4 @@
# Copyright (c) 2016 Aleph Objects, Inc. # Copyright (c) 2017 Aleph Objects, Inc.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application from UM.Application import Application
@ -40,7 +40,8 @@ class GCodeReader(MeshReader):
self._extruder_number = 0 self._extruder_number = 0
self._clearValues() self._clearValues()
self._scene_node = None self._scene_node = None
self._position = namedtuple('Position', ['x', 'y', 'z', 'e']) # X, Y, Z position, F feedrate and E extruder values are stored
self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e'])
self._is_layers_in_file = False # Does the Gcode have the layers comment? self._is_layers_in_file = False # Does the Gcode have the layers comment?
self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset] self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
self._current_layer_thickness = 0.2 # default self._current_layer_thickness = 0.2 # default
@ -48,12 +49,16 @@ class GCodeReader(MeshReader):
Preferences.getInstance().addPreference("gcodereader/show_caution", True) Preferences.getInstance().addPreference("gcodereader/show_caution", True)
def _clearValues(self): def _clearValues(self):
self._filament_diameter = 2.85
self._extruder_number = 0 self._extruder_number = 0
self._extrusion_length_offset = [0]
self._layer_type = LayerPolygon.Inset0Type self._layer_type = LayerPolygon.Inset0Type
self._layer_number = 0 self._layer_number = 0
self._previous_z = 0 self._previous_z = 0
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder() self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
self._center_is_zero = False self._center_is_zero = False
self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91)
self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83)
@staticmethod @staticmethod
def _getValue(line, code): def _getValue(line, code):
@ -96,7 +101,7 @@ class GCodeReader(MeshReader):
def _createPolygon(self, layer_thickness, path, extruder_offsets): def _createPolygon(self, layer_thickness, path, extruder_offsets):
countvalid = 0 countvalid = 0
for point in path: for point in path:
if point[3] > 0: if point[5] > 0:
countvalid += 1 countvalid += 1
if countvalid >= 2: if countvalid >= 2:
# we know what to do now, no need to count further # we know what to do now, no need to count further
@ -114,46 +119,89 @@ class GCodeReader(MeshReader):
line_types = numpy.empty((count - 1, 1), numpy.int32) line_types = numpy.empty((count - 1, 1), numpy.int32)
line_widths = numpy.empty((count - 1, 1), numpy.float32) line_widths = numpy.empty((count - 1, 1), numpy.float32)
line_thicknesses = numpy.empty((count - 1, 1), numpy.float32) line_thicknesses = numpy.empty((count - 1, 1), numpy.float32)
# TODO: need to calculate actual line width based on E values line_feedrates = numpy.empty((count - 1, 1), numpy.float32)
line_widths[:, 0] = 0.35 # Just a guess line_widths[:, 0] = 0.35 # Just a guess
line_thicknesses[:, 0] = layer_thickness line_thicknesses[:, 0] = layer_thickness
points = numpy.empty((count, 3), numpy.float32) points = numpy.empty((count, 3), numpy.float32)
extrusion_values = numpy.empty((count, 1), numpy.float32)
i = 0 i = 0
for point in path: for point in path:
points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]] points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]]
extrusion_values[i] = point[4]
if i > 0: if i > 0:
line_types[i - 1] = point[3] line_feedrates[i - 1] = point[3]
if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: line_types[i - 1] = point[5]
if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
line_widths[i - 1] = 0.1 line_widths[i - 1] = 0.1
line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines
else:
line_widths[i - 1] = self._calculateLineWidth(points[i], points[i-1], extrusion_values[i], extrusion_values[i-1], layer_thickness)
i += 1 i += 1
this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses) this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses, line_feedrates)
this_poly.buildCache() this_poly.buildCache()
this_layer.polygons.append(this_poly) this_layer.polygons.append(this_poly)
return True return True
def _createEmptyLayer(self, layer_number):
self._layer_data_builder.addLayer(layer_number)
self._layer_data_builder.setLayerHeight(layer_number, 0)
self._layer_data_builder.setLayerThickness(layer_number, 0)
def _calculateLineWidth(self, current_point, previous_point, current_extrusion, previous_extrusion, layer_thickness):
# Area of the filament
Af = (self._filament_diameter / 2) ** 2 * numpy.pi
# Length of the extruded filament
de = current_extrusion - previous_extrusion
# Volumne of the extruded filament
dVe = de * Af
# Length of the printed line
dX = numpy.sqrt((current_point[0] - previous_point[0])**2 + (current_point[2] - previous_point[2])**2)
# When the extruder recovers from a retraction, we get zero distance
if dX == 0:
return 0.1
# Area of the printed line. This area is a rectangle
Ae = dVe / dX
# This area is a rectangle with area equal to layer_thickness * layer_width
line_width = Ae / layer_thickness
# A threshold is set to avoid weird paths in the GCode
if line_width > 1.2:
return 0.35
return line_width
def _gCode0(self, position, params, path): def _gCode0(self, position, params, path):
x, y, z, e = position x, y, z, f, e = position
if self._is_absolute_positioning:
x = params.x if params.x is not None else x x = params.x if params.x is not None else x
y = params.y if params.y is not None else y y = params.y if params.y is not None else y
z = params.z if params.z is not None else position.z z = params.z if params.z is not None else z
else:
x += params.x if params.x is not None else 0
y += params.y if params.y is not None else 0
z += params.z if params.z is not None else 0
f = params.f if params.f is not None else f
if params.e is not None: if params.e is not None:
if params.e > e[self._extruder_number]: new_extrusion_value = params.e if self._is_absolute_extrusion else e[self._extruder_number] + params.e
path.append([x, y, z, self._layer_type]) # extrusion if new_extrusion_value > e[self._extruder_number]:
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
else: else:
path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
e[self._extruder_number] = params.e e[self._extruder_number] = new_extrusion_value
# Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions # Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions
# Also, 1.5 is a heuristic for any priming or whatsoever, we skip those. # Also, 1.5 is a heuristic for any priming or whatsoever, we skip those.
if z > self._previous_z and (z - self._previous_z < 1.5): if z > self._previous_z and (z - self._previous_z < 1.5):
self._current_layer_thickness = z - self._previous_z + 0.05 # allow a tiny overlap self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
self._previous_z = z self._previous_z = z
else: else:
path.append([x, y, z, LayerPolygon.MoveCombingType]) path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
return self._position(x, y, z, e) return self._position(x, y, z, f, e)
# G0 and G1 should be handled exactly the same. # G0 and G1 should be handled exactly the same.
_gCode1 = _gCode0 _gCode1 = _gCode0
@ -164,17 +212,31 @@ class GCodeReader(MeshReader):
params.x if params.x is not None else position.x, params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y, params.y if params.y is not None else position.y,
0, 0,
position.f,
position.e) position.e)
## Set the absolute positioning
def _gCode90(self, position, params, path):
self._is_absolute_positioning = True
return position
## Set the relative positioning
def _gCode91(self, position, params, path):
self._is_absolute_positioning = False
return position
## Reset the current position to the values specified. ## Reset the current position to the values specified.
# For example: G92 X10 will set the X to 10 without any physical motion. # For example: G92 X10 will set the X to 10 without any physical motion.
def _gCode92(self, position, params, path): def _gCode92(self, position, params, path):
if params.e is not None: if params.e is not None:
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
position.e[self._extruder_number] = params.e position.e[self._extruder_number] = params.e
return self._position( return self._position(
params.x if params.x is not None else position.x, params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y, params.y if params.y is not None else position.y,
params.z if params.z is not None else position.z, params.z if params.z is not None else position.z,
params.f if params.f is not None else position.f,
position.e) position.e)
def _processGCode(self, G, line, position, path): def _processGCode(self, G, line, position, path):
@ -182,7 +244,7 @@ class GCodeReader(MeshReader):
line = line.split(";", 1)[0] # Remove comments (if any) line = line.split(";", 1)[0] # Remove comments (if any)
if func is not None: if func is not None:
s = line.upper().split(" ") s = line.upper().split(" ")
x, y, z, e = None, None, None, None x, y, z, f, e = None, None, None, None, None
for item in s[1:]: for item in s[1:]:
if len(item) <= 1: if len(item) <= 1:
continue continue
@ -194,20 +256,31 @@ class GCodeReader(MeshReader):
y = float(item[1:]) y = float(item[1:])
if item[0] == "Z": if item[0] == "Z":
z = float(item[1:]) z = float(item[1:])
if item[0] == "F":
f = float(item[1:]) / 60
if item[0] == "E": if item[0] == "E":
e = float(item[1:]) e = float(item[1:])
if (x is not None and x < 0) or (y is not None and y < 0): if self._is_absolute_positioning and ((x is not None and x < 0) or (y is not None and y < 0)):
self._center_is_zero = True self._center_is_zero = True
params = self._position(x, y, z, e) params = self._position(x, y, z, f, e)
return func(position, params, path) return func(position, params, path)
return position return position
def _processTCode(self, T, line, position, path): def _processTCode(self, T, line, position, path):
self._extruder_number = T self._extruder_number = T
if self._extruder_number + 1 > len(position.e): if self._extruder_number + 1 > len(position.e):
self._extrusion_length_offset.extend([0] * (self._extruder_number - len(position.e) + 1))
position.e.extend([0] * (self._extruder_number - len(position.e) + 1)) position.e.extend([0] * (self._extruder_number - len(position.e) + 1))
return position return position
def _processMCode(self, M):
if M == 82:
# Set absolute extrusion mode
self._is_absolute_extrusion = True
elif M == 83:
# Set relative extrusion mode
self._is_absolute_extrusion = False
_type_keyword = ";TYPE:" _type_keyword = ";TYPE:"
_layer_keyword = ";LAYER:" _layer_keyword = ";LAYER:"
@ -223,6 +296,8 @@ class GCodeReader(MeshReader):
def read(self, file_name): def read(self, file_name):
Logger.log("d", "Preparing to load %s" % file_name) Logger.log("d", "Preparing to load %s" % file_name)
self._cancelled = False self._cancelled = False
# We obtain the filament diameter from the selected printer to calculate line widths
self._filament_diameter = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value")
scene_node = SceneNode() scene_node = SceneNode()
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no # Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
@ -260,8 +335,11 @@ class GCodeReader(MeshReader):
Logger.log("d", "Parsing %s..." % file_name) Logger.log("d", "Parsing %s..." % file_name)
current_position = self._position(0, 0, 0, [0]) current_position = self._position(0, 0, 0, 0, [0])
current_path = [] current_path = []
min_layer_number = 0
negative_layers = 0
previous_layer = 0
for line in file: for line in file:
if self._cancelled: if self._cancelled:
@ -293,12 +371,29 @@ class GCodeReader(MeshReader):
else: else:
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type) Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
# When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder
if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
try: try:
layer_number = int(line[len(self._layer_keyword):]) layer_number = int(line[len(self._layer_keyword):])
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
current_path.clear() current_path.clear()
# When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior
# as in ProcessSlicedLayersJob
if layer_number < min_layer_number:
min_layer_number = layer_number
if layer_number < 0:
layer_number += abs(min_layer_number)
negative_layers += 1
else:
layer_number += negative_layers
# In case there is a gap in the layer count, empty layers are created
for empty_layer in range(previous_layer + 1, layer_number):
self._createEmptyLayer(empty_layer)
self._layer_number = layer_number self._layer_number = layer_number
previous_layer = layer_number
except: except:
pass pass
@ -308,17 +403,12 @@ class GCodeReader(MeshReader):
G = self._getInt(line, "G") G = self._getInt(line, "G")
if G is not None: if G is not None:
# When find a movement, the new posistion is calculated and added to the current_path, but
# don't need to create a polygon until the end of the layer
current_position = self._processGCode(G, line, current_position, current_path) current_position = self._processGCode(G, line, current_position, current_path)
# < 2 is a heuristic for a movement only, that should not be counted as a layer
if current_position.z > last_z and abs(current_position.z - last_z) < 2:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
current_path.clear()
if not self._is_layers_in_file:
self._layer_number += 1
continue continue
# When changing the extruder, the polygon with the stored paths is computed
if line.startswith("T"): if line.startswith("T"):
T = self._getInt(line, "T") T = self._getInt(line, "T")
if T is not None: if T is not None:
@ -327,8 +417,12 @@ class GCodeReader(MeshReader):
current_position = self._processTCode(T, line, current_position, current_path) current_position = self._processTCode(T, line, current_position, current_path)
# "Flush" leftovers if line.startswith("M"):
if not self._is_layers_in_file and len(current_path) > 1: M = self._getInt(line, "M")
self._processMCode(M)
# "Flush" leftovers. Last layer paths are still stored
if len(current_path) > 1:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])): if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
self._layer_number += 1 self._layer_number += 1
current_path.clear() current_path.clear()

View file

@ -1,113 +0,0 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Resources import Resources
from UM.Scene.SceneNode import SceneNode
from UM.Scene.ToolHandle import ToolHandle
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.View.RenderPass import RenderPass
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
from cura.Settings.ExtruderManager import ExtruderManager
import os.path
## RenderPass used to display g-code paths.
class LayerPass(RenderPass):
def __init__(self, width, height):
super().__init__("layerview", width, height)
self._layer_shader = None
self._tool_handle_shader = None
self._gl = OpenGL.getInstance().getBindingsObject()
self._scene = Application.getInstance().getController().getScene()
self._extruder_manager = ExtruderManager.getInstance()
self._layer_view = None
self._compatibility_mode = None
def setLayerView(self, layerview):
self._layer_view = layerview
self._compatibility_mode = layerview.getCompatibilityMode()
def render(self):
if not self._layer_shader:
if self._compatibility_mode:
shader_filename = "layers.shader"
else:
shader_filename = "layers3d.shader"
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), shader_filename))
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
if self._layer_view:
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getLayerViewType())
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers())
self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
else:
#defaults
self._layer_shader.setUniformValue("u_layer_view_type", 1)
self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1])
self._layer_shader.setUniformValue("u_show_travel_moves", 0)
self._layer_shader.setUniformValue("u_show_helpers", 1)
self._layer_shader.setUniformValue("u_show_skin", 1)
self._layer_shader.setUniformValue("u_show_infill", 1)
if not self._tool_handle_shader:
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
for node in DepthFirstIterator(self._scene.getRoot()):
if isinstance(node, ToolHandle):
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
# Render all layers below a certain number as line mesh instead of vertices.
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
start = 0
end = 0
element_counts = layer_data.getElementCounts()
for layer in sorted(element_counts.keys()):
if layer > self._layer_view._current_layer_num:
break
if self._layer_view._minimum_layer_num > layer:
start += element_counts[layer]
end += element_counts[layer]
# This uses glDrawRangeElements internally to only draw a certain range of lines.
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
batch.addItem(node.getWorldTransformation(), layer_data)
batch.render(self._scene.getActiveCamera())
# Create a new batch that is not range-limited
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
if self._layer_view.getCurrentLayerMesh():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh())
if self._layer_view.getCurrentLayerJumps():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps())
if len(batch.items) > 0:
batch.render(self._scene.getActiveCamera())
# Render toolhandles on top of the layerview
if len(tool_handle_batch.items) > 0:
tool_handle_batch.render(self._scene.getActiveCamera())
self.release()

View file

@ -1,388 +0,0 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item
{
id: base
width: {
if (UM.LayerView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
} else {
return UM.Theme.getSize("layerview_menu_size").width;
}
}
height: {
if (UM.LayerView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
} else {
return UM.Theme.getSize("layerview_menu_size").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
}
property var buttonTarget: {
if(parent != null)
{
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
}
return Qt.point(0,0)
}
visible: parent != null ? !parent.parent.monitoringPrint: true
UM.PointingRectangle {
id: layerViewMenu
anchors.right: parent.right
anchors.top: parent.top
width: parent.width
height: parent.height
z: slider.z - 1
color: UM.Theme.getColor("tool_panel_background")
borderWidth: UM.Theme.getSize("default_lining").width
borderColor: UM.Theme.getColor("lining")
arrowSize: 0 // hide arrow until weird issue with first time rendering is fixed
ColumnLayout {
id: view_settings
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
// if we are in compatibility mode, we only show the "line type"
property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("layerview_row_spacing").height
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
Label
{
id: layerViewTypesLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Color scheme")
font: UM.Theme.getFont("default");
visible: !UM.LayerView.compatibilityMode
Layout.fillWidth: true
color: UM.Theme.getColor("setting_control_text")
}
ListModel // matches LayerView.py
{
id: layerViewTypes
}
Component.onCompleted:
{
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Material Color"),
type_id: 0
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Line Type"),
type_id: 1 // these ids match the switching in the shader
})
}
ComboBox
{
id: layerTypeCombobox
anchors.left: parent.left
Layout.fillWidth: true
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
model: layerViewTypes
visible: !UM.LayerView.compatibilityMode
style: UM.Theme.styles.combobox
anchors.right: parent.right
anchors.rightMargin: 10 * screenScaleFactor
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
Component.onCompleted:
{
currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// update visibility of legends
view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1);
}
}
Label
{
id: compatibilityModeLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Compatibility Mode")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
visible: UM.LayerView.compatibilityMode
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
Label
{
id: space2Label
anchors.left: parent.left
text: " "
font.pointSize: 0.5
}
Connections {
target: UM.Preferences
onPreferenceChanged:
{
layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
view_settings.show_skin = UM.Preferences.getValue("layerview/show_skin");
view_settings.show_infill = UM.Preferences.getValue("layerview/show_infill");
view_settings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
view_settings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
}
}
Repeater {
model: Cura.ExtrudersModel{}
CheckBox {
id: extrudersModelCheckBox
checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
onClicked: {
view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
}
visible: !UM.LayerView.compatibilityMode
enabled: index + 1 <= 4
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: extrudersModelCheckBox.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: model.color
radius: width / 2
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: !view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: model.name
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
anchors.verticalCenter: parent.verticalCenter
anchors.left: extrudersModelCheckBox.left;
anchors.right: extrudersModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
Repeater {
model: ListModel {
id: typesLegenModel
Component.onCompleted:
{
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Travels"),
initialValue: view_settings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Helpers"),
initialValue: view_settings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Shell"),
initialValue: view_settings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Infill"),
initialValue: view_settings.show_infill,
preference: "layerview/show_infill",
colorId: "layerview_infill"
});
}
}
CheckBox {
id: legendModelCheckBox
checked: model.initialValue
onClicked: {
UM.Preferences.setValue(model.preference, checked);
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: legendModelCheckBox.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: label
font: UM.Theme.getFont("default")
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
anchors.verticalCenter: parent.verticalCenter
anchors.left: legendModelCheckBox.left;
anchors.right: legendModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
CheckBox {
checked: view_settings.only_show_top_layers
onClicked: {
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
}
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.LayerView.compatibilityMode
style: UM.Theme.styles.checkbox
}
CheckBox {
checked: view_settings.top_layer_count == 5
onClicked: {
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
}
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
visible: UM.LayerView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Repeater {
model: ListModel {
id: typesLegenModelNoCheck
Component.onCompleted:
{
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin",
});
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x",
});
}
}
Label {
text: label
visible: view_settings.show_legend
id: typesLegendModelLabel
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: typesLegendModelLabel.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
}
}
LayerSlider {
id: slider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("layerview_menu_size").height
anchors {
top: parent.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
right: layerViewMenu.right
rightMargin: UM.Theme.getSize("slider_layerview_margin").width
}
// custom properties
upperValue: UM.LayerView.currentLayer
lowerValue: UM.LayerView.minimumLayer
maximumValue: UM.LayerView.numLayers
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
upperHandleColor: UM.Theme.getColor("slider_handle")
lowerHandleColor: UM.Theme.getColor("slider_handle")
rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
layersVisible: UM.LayerView.layerActivity && CuraApplication.platformActivity ? true : false
// update values when layer data changes
Connections {
target: UM.LayerView
onMaxLayersChanged: slider.setUpperValue(UM.LayerView.currentLayer)
onMinimumLayerChanged: slider.setLowerValue(UM.LayerView.minimumLayer)
onCurrentLayerChanged: slider.setUpperValue(UM.LayerView.currentLayer)
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
slider.setLowerValue(UM.LayerView.minimumLayer)
slider.setUpperValue(UM.LayerView.currentLayer)
}
}
}
FontMetrics {
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -1,151 +0,0 @@
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
import LayerView
class LayerViewProxy(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._current_layer = 0
self._controller = Application.getInstance().getController()
self._controller.activeViewChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
currentLayerChanged = pyqtSignal()
maxLayersChanged = pyqtSignal()
activityChanged = pyqtSignal()
globalStackChanged = pyqtSignal()
preferencesChanged = pyqtSignal()
busyChanged = pyqtSignal()
@pyqtProperty(bool, notify=activityChanged)
def layerActivity(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getActivity()
@pyqtProperty(int, notify=maxLayersChanged)
def numLayers(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getMaxLayers()
@pyqtProperty(int, notify=currentLayerChanged)
def currentLayer(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getCurrentLayer()
@pyqtProperty(int, notify=currentLayerChanged)
def minimumLayer(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getMinimumLayer()
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.isBusy()
return False
@pyqtProperty(bool, notify=preferencesChanged)
def compatibilityMode(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getCompatibilityMode()
return False
@pyqtSlot(int)
def setCurrentLayer(self, layer_num):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setLayer(layer_num)
@pyqtSlot(int)
def setMinimumLayer(self, layer_num):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setMinimumLayer(layer_num)
@pyqtSlot(int)
def setLayerViewType(self, layer_view_type):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setLayerViewType(layer_view_type)
@pyqtSlot(result=int)
def getLayerViewType(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getLayerViewType()
return 0
# Opacity 0..1
@pyqtSlot(int, float)
def setExtruderOpacity(self, extruder_nr, opacity):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setExtruderOpacity(extruder_nr, opacity)
@pyqtSlot(int)
def setShowTravelMoves(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowTravelMoves(show)
@pyqtSlot(int)
def setShowHelpers(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowHelpers(show)
@pyqtSlot(int)
def setShowSkin(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowSkin(show)
@pyqtSlot(int)
def setShowInfill(self, show):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.setShowInfill(show)
@pyqtProperty(int, notify=globalStackChanged)
def extruderCount(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
return active_view.getExtruderCount()
return 0
def _layerActivityChanged(self):
self.activityChanged.emit()
def _onLayerChanged(self):
self.currentLayerChanged.emit()
self._layerActivityChanged()
def _onMaxLayersChanged(self):
self.maxLayersChanged.emit()
def _onBusyChanged(self):
self.busyChanged.emit()
def _onGlobalStackChanged(self):
self.globalStackChanged.emit()
def _onPreferencesChanged(self):
self.preferencesChanged.emit()
def _onActiveViewChanged(self):
active_view = self._controller.getActiveView()
if type(active_view) == LayerView.LayerView.LayerView:
active_view.currentLayerNumChanged.connect(self._onLayerChanged)
active_view.maxLayersChanged.connect(self._onMaxLayersChanged)
active_view.busyChanged.connect(self._onBusyChanged)
active_view.globalStackChanged.connect(self._onGlobalStackChanged)
active_view.preferencesChanged.connect(self._onPreferencesChanged)

View file

@ -1,25 +0,0 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import LayerView, LayerViewProxy
from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
"view": {
"name": catalog.i18nc("@item:inlistbox", "Layer view"),
"view_panel": "LayerView.qml",
"weight": 2
}
}
def createLayerViewProxy(engine, script_engine):
return LayerViewProxy.LayerViewProxy()
def register(app):
layer_view = LayerView.LayerView()
qmlRegisterSingletonType(LayerViewProxy.LayerViewProxy, "UM", 1, 0, "LayerView", layer_view.getProxy)
return { "view": LayerView.LayerView() }

View file

@ -116,8 +116,7 @@ class MachineSettingsAction(MachineAction):
@pyqtSlot(int) @pyqtSlot(int)
def setMachineExtruderCount(self, extruder_count): def setMachineExtruderCount(self, extruder_count):
machine_manager = Application.getInstance().getMachineManager() extruder_manager = Application.getInstance().getExtruderManager()
extruder_manager = ExtruderManager.getInstance()
definition_changes_container = self._global_container_stack.definitionChanges definition_changes_container = self._global_container_stack.definitionChanges
if not self._global_container_stack or definition_changes_container == self._empty_container: if not self._global_container_stack or definition_changes_container == self._empty_container:
@ -127,38 +126,16 @@ class MachineSettingsAction(MachineAction):
if extruder_count == previous_extruder_count: if extruder_count == previous_extruder_count:
return return
extruder_material_id = None # reset all extruder number settings whose value is no longer valid
extruder_variant_id = None for setting_instance in self._global_container_stack.userChanges.findInstances():
if extruder_count == 1:
# Get the material and variant of the first extruder before setting the number extruders to 1
if machine_manager.hasMaterials:
extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]]
if machine_manager.hasVariants:
extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]]
# Copy any settable_per_extruder setting value from the extruders to the global stack
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings
global_user_container = self._global_container_stack.getTop()
for extruder_stack in extruder_stacks:
extruder_index = extruder_stack.getMetaDataEntry("position")
extruder_user_container = extruder_stack.getTop()
for setting_instance in extruder_user_container.findInstances():
setting_key = setting_instance.definition.key setting_key = setting_instance.definition.key
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
if settable_per_extruder: continue
limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder")
if limit_to_extruder == "-1" or limit_to_extruder == extruder_index: old_value = int(self._global_container_stack.userChanges.getProperty(setting_key, "value"))
global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value")) if old_value >= extruder_count:
extruder_user_container.removeInstance(setting_key) self._global_container_stack.userChanges.removeInstance(setting_key)
Logger.log("d", "Reset [%s] because its old value [%s] is no longer valid ", setting_key, old_value)
# Check to see if any features are set to print with an extruder that will no longer exist
for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]:
if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count - 1:
Logger.log("i", "Lowering %s setting to match number of extruders", setting_key)
self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count - 1)
# Check to see if any objects are set to print with an extruder that will no longer exist # Check to see if any objects are set to print with an extruder that will no longer exist
root_node = Application.getInstance().getController().getScene().getRoot() root_node = Application.getInstance().getController().getScene().getRoot()
@ -171,14 +148,13 @@ class MachineSettingsAction(MachineAction):
definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count) definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
if extruder_count > 1:
# Multiextrusion
# Make sure one of the extruder stacks is active # Make sure one of the extruder stacks is active
if extruder_manager.activeExtruderIndex == -1:
extruder_manager.setActiveExtruderIndex(0) extruder_manager.setActiveExtruderIndex(0)
# Move settable_per_extruder values out of the global container # Move settable_per_extruder values out of the global container
# After CURA-4482 this should not be the case anymore, but we still want to support older project files.
global_user_container = self._global_container_stack.getTop()
if previous_extruder_count == 1: if previous_extruder_count == 1:
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
global_user_container = self._global_container_stack.getTop() global_user_container = self._global_container_stack.getTop()
@ -186,37 +162,15 @@ class MachineSettingsAction(MachineAction):
for setting_instance in global_user_container.findInstances(): for setting_instance in global_user_container.findInstances():
setting_key = setting_instance.definition.key setting_key = setting_instance.definition.key
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder") settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
if settable_per_extruder: if settable_per_extruder:
limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
extruder_stack = extruder_stacks[max(0, limit_to_extruder)] extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
global_user_container.removeInstance(setting_key) global_user_container.removeInstance(setting_key)
else:
# Single extrusion
# Make sure the machine stack is active
if extruder_manager.activeExtruderIndex > -1:
extruder_manager.setActiveExtruderIndex(-1)
# Restore material and variant on global stack
# MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines
if extruder_material_id or extruder_variant_id:
# Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes
# The dialog is not relevant here, since we're restoring the previous situation as good as possible
preferences = Preferences.getInstance()
choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override")
preferences.setValue("cura/choice_on_profile_override", "always_keep")
if extruder_material_id:
machine_manager.setActiveMaterial(extruder_material_id)
if extruder_variant_id:
machine_manager.setActiveVariant(extruder_variant_id)
preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override)
self.forceUpdate() self.forceUpdate()
@pyqtSlot() @pyqtSlot()
def forceUpdate(self): def forceUpdate(self):
# Force rebuilding the build volume by reloading the global container stack. # Force rebuilding the build volume by reloading the global container stack.
@ -269,16 +223,13 @@ class MachineSettingsAction(MachineAction):
if not self._global_container_stack.getMetaDataEntry("has_materials", False): if not self._global_container_stack.getMetaDataEntry("has_materials", False):
return return
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
if machine_extruder_count > 1:
material = ExtruderManager.getInstance().getActiveExtruderStack().material material = ExtruderManager.getInstance().getActiveExtruderStack().material
else:
material = self._global_container_stack.material
material_diameter = material.getProperty("material_diameter", "value") material_diameter = material.getProperty("material_diameter", "value")
if not material_diameter: # in case of "empty" material if not material_diameter:
# in case of "empty" material
material_diameter = 0 material_diameter = 0
material_approximate_diameter = str(round(material_diameter))
material_approximate_diameter = str(round(material_diameter))
definition_changes = self._global_container_stack.definitionChanges definition_changes = self._global_container_stack.definitionChanges
machine_diameter = definition_changes.getProperty("material_diameter", "value") machine_diameter = definition_changes.getProperty("material_diameter", "value")
if not machine_diameter: if not machine_diameter:
@ -288,10 +239,7 @@ class MachineSettingsAction(MachineAction):
if material_approximate_diameter != machine_approximate_diameter: if material_approximate_diameter != machine_approximate_diameter:
Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.") Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
if machine_extruder_count > 1:
stacks = ExtruderManager.getInstance().getExtruderStacks() stacks = ExtruderManager.getInstance().getExtruderStacks()
else:
stacks = [self._global_container_stack]
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
materials_definition = self._global_container_stack.definition.getId() materials_definition = self._global_container_stack.definition.getId()
@ -332,7 +280,7 @@ class MachineSettingsAction(MachineAction):
search_criteria["id"] = stack.getMetaDataEntry("preferred_material") search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
materials = self._container_registry.findInstanceContainers(**search_criteria) materials = self._container_registry.findInstanceContainers(**search_criteria)
if not materials: if not materials:
# Preferrd material with new diameter is not found, search for any material # Preferred material with new diameter is not found, search for any material
search_criteria.pop("id", None) search_criteria.pop("id", None)
materials = self._container_registry.findInstanceContainers(**search_criteria) materials = self._container_registry.findInstanceContainers(**search_criteria)
if not materials: if not materials:

View file

@ -78,21 +78,16 @@ class PerObjectSettingsTool(Tool):
def _onGlobalContainerChanged(self): def _onGlobalContainerChanged(self):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
# used for enabling or disabling per extruder settings per object
self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
# Ensure that all extruder data is reset extruder_stack = ExtruderManager.getInstance().getExtruderStack(0)
if not self._multi_extrusion:
default_stack_id = global_container_stack.getId()
else:
default_stack = ExtruderManager.getInstance().getExtruderStack(0)
if default_stack:
default_stack_id = default_stack.getId()
else:
default_stack_id = global_container_stack.getId()
if extruder_stack:
root_node = Application.getInstance().getController().getScene().getRoot() root_node = Application.getInstance().getController().getScene().getRoot()
for node in DepthFirstIterator(root_node): for node in DepthFirstIterator(root_node):
new_stack_id = default_stack_id new_stack_id = extruder_stack.getId()
# Get position of old extruder stack for this node # Get position of old extruder stack for this node
old_extruder_pos = node.callDecoration("getActiveExtruderPosition") old_extruder_pos = node.callDecoration("getActiveExtruderPosition")
if old_extruder_pos is not None: if old_extruder_pos is not None:

View file

@ -114,7 +114,7 @@ UM.Dialog
anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.rightMargin: UM.Theme.getSize("default_margin").width
Label Label
{ {
text: "<b>" + model.name + "</b> - " + model.author text: "<b>" + model.name + "</b>" + ((model.author !== "") ? (" - " + model.author) : "")
width: contentWidth width: contentWidth
height: contentHeight + UM.Theme.getSize("default_margin").height height: contentHeight + UM.Theme.getSize("default_margin").height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View file

@ -4,8 +4,6 @@
from . import RemovableDrivePlugin from . import RemovableDrivePlugin
from UM.Logger import Logger
import subprocess import subprocess
import os import os
@ -15,12 +13,12 @@ import plistlib
class OSXRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin): class OSXRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
def checkRemovableDrives(self): def checkRemovableDrives(self):
drives = {} drives = {}
p = subprocess.Popen(["system_profiler", "SPUSBDataType", "-xml"], stdout = subprocess.PIPE) p = subprocess.Popen(["system_profiler", "SPUSBDataType", "-xml"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
plist = plistlib.loads(p.communicate()[0]) plist = plistlib.loads(p.communicate()[0])
result = self._recursiveSearch(plist, "removable_media") result = self._recursiveSearch(plist, "removable_media")
p = subprocess.Popen(["system_profiler", "SPCardReaderDataType", "-xml"], stdout=subprocess.PIPE) p = subprocess.Popen(["system_profiler", "SPCardReaderDataType", "-xml"], stdout=subprocess.PIPE, stderr = subprocess.PIPE)
plist = plistlib.loads(p.communicate()[0]) plist = plistlib.loads(p.communicate()[0])
result.extend(self._recursiveSearch(plist, "removable_media")) result.extend(self._recursiveSearch(plist, "removable_media"))

View file

@ -19,6 +19,7 @@ Item {
property color upperHandleColor: "black" property color upperHandleColor: "black"
property color lowerHandleColor: "black" property color lowerHandleColor: "black"
property color rangeHandleColor: "black" property color rangeHandleColor: "black"
property color handleActiveColor: "white"
property real handleLabelWidth: width property real handleLabelWidth: width
property var activeHandle: upperHandle property var activeHandle: upperHandle
@ -100,8 +101,8 @@ Item {
var lowerValue = sliderRoot.getLowerValueFromSliderHandle() var lowerValue = sliderRoot.getLowerValueFromSliderHandle()
// set both values after moving the handle position // set both values after moving the handle position
UM.LayerView.setCurrentLayer(upperValue) UM.SimulationView.setCurrentLayer(upperValue)
UM.LayerView.setMinimumLayer(lowerValue) UM.SimulationView.setMinimumLayer(lowerValue)
} }
function setValue (value) { function setValue (value) {
@ -109,8 +110,8 @@ Item {
value = Math.min(value, sliderRoot.maximumValue) value = Math.min(value, sliderRoot.maximumValue)
value = Math.max(value, sliderRoot.minimumValue + range) value = Math.max(value, sliderRoot.minimumValue + range)
UM.LayerView.setCurrentLayer(value) UM.SimulationView.setCurrentLayer(value)
UM.LayerView.setMinimumLayer(value - range) UM.SimulationView.setMinimumLayer(value - range)
} }
Rectangle { Rectangle {
@ -134,7 +135,7 @@ Item {
onPressed: sliderRoot.setActiveHandle(rangeHandle) onPressed: sliderRoot.setActiveHandle(rangeHandle)
} }
LayerSliderLabel { SimulationSliderLabel {
id: rangleHandleLabel id: rangleHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -146,7 +147,7 @@ Item {
// custom properties // custom properties
maximumValue: sliderRoot.maximumValue maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue value: sliderRoot.upperValue
busy: UM.LayerView.busy busy: UM.SimulationView.busy
setValue: rangeHandle.setValue // connect callback functions setValue: rangeHandle.setValue // connect callback functions
} }
} }
@ -160,7 +161,7 @@ Item {
height: sliderRoot.handleSize height: sliderRoot.handleSize
anchors.horizontalCenter: sliderRoot.horizontalCenter anchors.horizontalCenter: sliderRoot.horizontalCenter
radius: sliderRoot.handleRadius radius: sliderRoot.handleRadius
color: sliderRoot.upperHandleColor color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
visible: sliderRoot.layersVisible visible: sliderRoot.layersVisible
function onHandleDragged () { function onHandleDragged () {
@ -174,7 +175,7 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
// set the new value after moving the handle position // set the new value after moving the handle position
UM.LayerView.setCurrentLayer(getValue()) UM.SimulationView.setCurrentLayer(getValue())
} }
// get the upper value based on the slider position // get the upper value based on the slider position
@ -188,7 +189,7 @@ Item {
// set the slider position based on the upper value // set the slider position based on the upper value
function setValue (value) { function setValue (value) {
UM.LayerView.setCurrentLayer(value) UM.SimulationView.setCurrentLayer(value)
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
@ -198,6 +199,9 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
} }
Keys.onUpPressed: upperHandleLabel.setValue(upperHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging // dragging
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -210,10 +214,13 @@ Item {
} }
onPositionChanged: parent.onHandleDragged() onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(upperHandle) onPressed: {
sliderRoot.setActiveHandle(upperHandle)
upperHandleLabel.forceActiveFocus()
}
} }
LayerSliderLabel { SimulationSliderLabel {
id: upperHandleLabel id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -225,7 +232,7 @@ Item {
// custom properties // custom properties
maximumValue: sliderRoot.maximumValue maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue value: sliderRoot.upperValue
busy: UM.LayerView.busy busy: UM.SimulationView.busy
setValue: upperHandle.setValue // connect callback functions setValue: upperHandle.setValue // connect callback functions
} }
} }
@ -239,9 +246,9 @@ Item {
height: parent.handleSize height: parent.handleSize
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
radius: sliderRoot.handleRadius radius: sliderRoot.handleRadius
color: sliderRoot.lowerHandleColor color: lowerHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.lowerHandleColor
visible: slider.layersVisible visible: sliderRoot.layersVisible
function onHandleDragged () { function onHandleDragged () {
@ -254,7 +261,7 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
// set the new value after moving the handle position // set the new value after moving the handle position
UM.LayerView.setMinimumLayer(getValue()) UM.SimulationView.setMinimumLayer(getValue())
} }
// get the lower value from the current slider position // get the lower value from the current slider position
@ -268,7 +275,7 @@ Item {
// set the slider position based on the lower value // set the slider position based on the lower value
function setValue (value) { function setValue (value) {
UM.LayerView.setMinimumLayer(value) UM.SimulationView.setMinimumLayer(value)
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue) var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))) var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
@ -278,6 +285,9 @@ Item {
sliderRoot.updateRangeHandle() sliderRoot.updateRangeHandle()
} }
Keys.onUpPressed: lowerHandleLabel.setValue(lowerHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging // dragging
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -290,10 +300,13 @@ Item {
} }
onPositionChanged: parent.onHandleDragged() onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(lowerHandle) onPressed: {
sliderRoot.setActiveHandle(lowerHandle)
lowerHandleLabel.forceActiveFocus()
}
} }
LayerSliderLabel { SimulationSliderLabel {
id: lowerHandleLabel id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
@ -305,7 +318,7 @@ Item {
// custom properties // custom properties
maximumValue: sliderRoot.maximumValue maximumValue: sliderRoot.maximumValue
value: sliderRoot.lowerValue value: sliderRoot.lowerValue
busy: UM.LayerView.busy busy: UM.SimulationView.busy
setValue: lowerHandle.setValue // connect callback functions setValue: lowerHandle.setValue // connect callback functions
} }
} }

View file

@ -0,0 +1,49 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Math.Color import Color
from UM.Math.Vector import Vector
from UM.PluginRegistry import PluginRegistry
from UM.Scene.SceneNode import SceneNode
from UM.View.GL.OpenGL import OpenGL
from UM.Resources import Resources
import os
class NozzleNode(SceneNode):
def __init__(self, parent = None):
super().__init__(parent)
self._shader = None
self.setCalculateBoundingBox(False)
self._createNozzleMesh()
def _createNozzleMesh(self):
mesh_file = "resources/nozzle.stl"
try:
path = os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), mesh_file)
except FileNotFoundError:
path = ""
reader = Application.getInstance().getMeshFileHandler().getReaderForFile(path)
node = reader.read(path)
if node.getMeshData():
self.setMeshData(node.getMeshData())
def render(self, renderer):
# Avoid to render if it is not visible
if not self.isVisible():
return False
if not self._shader:
# We now misuse the platform shader, as it actually supports textures
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
self._shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb()))
# Set the opacity to 0, so that the template is in full control.
self._shader.setUniformValue("u_opacity", 0)
if self.getMeshData():
renderer.queueNode(self, shader = self._shader, transparent = True)
return True

View file

@ -0,0 +1,161 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item {
id: sliderRoot
// handle properties
property real handleSize: 10
property real handleRadius: handleSize / 2
property color handleColor: "black"
property color handleActiveColor: "white"
property color rangeColor: "black"
property real handleLabelWidth: width
// track properties
property real trackThickness: 4 // width of the slider track
property real trackRadius: trackThickness / 2
property color trackColor: "white"
property real trackBorderWidth: 1 // width of the slider track border
property color trackBorderColor: "black"
// value properties
property real maximumValue: 100
property bool roundValues: true
property real handleValue: maximumValue
property bool pathsVisible: true
function getHandleValueFromSliderHandle () {
return handle.getValue()
}
function setHandleValue (value) {
handle.setValue(value)
updateRangeHandle()
}
function updateRangeHandle () {
rangeHandle.width = handle.x - sliderRoot.handleSize
}
// slider track
Rectangle {
id: track
width: sliderRoot.width - sliderRoot.handleSize
height: sliderRoot.trackThickness
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
border.width: sliderRoot.trackBorderWidth
border.color: sliderRoot.trackBorderColor
visible: sliderRoot.pathsVisible
}
// Progress indicator
Item {
id: rangeHandle
x: handle.width
height: sliderRoot.handleSize
width: handle.x - sliderRoot.handleSize
anchors.verticalCenter: sliderRoot.verticalCenter
visible: sliderRoot.pathsVisible
Rectangle {
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
width: parent.width + sliderRoot.handleSize
anchors.centerIn: parent
color: sliderRoot.rangeColor
}
}
// Handle
Rectangle {
id: handle
x: sliderRoot.handleSize
width: sliderRoot.handleSize
height: sliderRoot.handleSize
anchors.verticalCenter: sliderRoot.verticalCenter
radius: sliderRoot.handleRadius
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
visible: sliderRoot.pathsVisible
function onHandleDragged () {
// update the range handle
sliderRoot.updateRangeHandle()
// set the new value after moving the handle position
UM.SimulationView.setCurrentPath(getValue())
}
// get the value based on the slider position
function getValue () {
var result = x / (sliderRoot.width - sliderRoot.handleSize)
result = result * sliderRoot.maximumValue
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
// set the slider position based on the value
function setValue (value) {
UM.SimulationView.setCurrentPath(value)
var diff = value / sliderRoot.maximumValue
var newXPosition = Math.round(diff * (sliderRoot.width - sliderRoot.handleSize))
x = newXPosition
// update the range handle
sliderRoot.updateRangeHandle()
}
Keys.onRightPressed: handleLabel.setValue(handleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
// dragging
MouseArea {
anchors.fill: parent
drag {
target: parent
axis: Drag.XAxis
minimumX: 0
maximumX: sliderRoot.width - sliderRoot.handleSize
}
onPressed: {
handleLabel.forceActiveFocus()
}
onPositionChanged: parent.onHandleDragged()
}
SimulationSliderLabel {
id: handleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
y: parent.y + sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
target: Qt.point(x + width / 2, sliderRoot.height)
visible: false
startFrom: 0
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.handleValue
busy: UM.SimulationView.busy
setValue: handle.setValue // connect callback functions
}
}
}

View file

@ -0,0 +1,190 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Math.Color import Color
from UM.Math.Vector import Vector
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Resources import Resources
from UM.Scene.SceneNode import SceneNode
from UM.Scene.ToolHandle import ToolHandle
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.View.RenderPass import RenderPass
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
from cura.Settings.ExtruderManager import ExtruderManager
import os.path
## RenderPass used to display g-code paths.
from .NozzleNode import NozzleNode
class SimulationPass(RenderPass):
def __init__(self, width, height):
super().__init__("simulationview", width, height)
self._layer_shader = None
self._layer_shadow_shader = None
self._current_shader = None # This shader will be the shadow or the normal depending if the user wants to see the paths or the layers
self._tool_handle_shader = None
self._nozzle_shader = None
self._old_current_layer = 0
self._old_current_path = 0
self._switching_layers = True # It tracks when the user is moving the layers' slider
self._gl = OpenGL.getInstance().getBindingsObject()
self._scene = Application.getInstance().getController().getScene()
self._extruder_manager = ExtruderManager.getInstance()
self._layer_view = None
self._compatibility_mode = None
def setSimulationView(self, layerview):
self._layer_view = layerview
self._compatibility_mode = layerview.getCompatibilityMode()
def render(self):
if not self._layer_shader:
if self._compatibility_mode:
shader_filename = "layers.shader"
shadow_shader_filename = "layers_shadow.shader"
else:
shader_filename = "layers3d.shader"
shadow_shader_filename = "layers3d_shadow.shader"
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shader_filename))
self._layer_shadow_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shadow_shader_filename))
self._current_shader = self._layer_shader
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
if self._layer_view:
self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
self._layer_shader.setUniformValue("u_max_thickness", self._layer_view.getMaxThickness())
self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness())
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType())
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers())
self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
else:
#defaults
self._layer_shader.setUniformValue("u_max_feedrate", 1)
self._layer_shader.setUniformValue("u_min_feedrate", 0)
self._layer_shader.setUniformValue("u_max_thickness", 1)
self._layer_shader.setUniformValue("u_min_thickness", 0)
self._layer_shader.setUniformValue("u_layer_view_type", 1)
self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1])
self._layer_shader.setUniformValue("u_show_travel_moves", 0)
self._layer_shader.setUniformValue("u_show_helpers", 1)
self._layer_shader.setUniformValue("u_show_skin", 1)
self._layer_shader.setUniformValue("u_show_infill", 1)
if not self._tool_handle_shader:
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
if not self._nozzle_shader:
self._nozzle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
self._nozzle_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb()))
self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
head_position = None # Indicates the current position of the print head
nozzle_node = None
for node in DepthFirstIterator(self._scene.getRoot()):
if isinstance(node, ToolHandle):
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
elif isinstance(node, NozzleNode):
nozzle_node = node
nozzle_node.setVisible(False)
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
# Render all layers below a certain number as line mesh instead of vertices.
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
start = 0
end = 0
element_counts = layer_data.getElementCounts()
for layer in sorted(element_counts.keys()):
# In the current layer, we show just the indicated paths
if layer == self._layer_view._current_layer_num:
# We look for the position of the head, searching the point of the current path
index = self._layer_view._current_path_num
offset = 0
for polygon in layer_data.getLayer(layer).polygons:
# The size indicates all values in the two-dimension array, and the second dimension is
# always size 3 because we have 3D points.
if index >= polygon.data.size // 3 - offset:
index -= polygon.data.size // 3 - offset
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
continue
# The head position is calculated and translated
head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition()
break
break
if self._layer_view._minimum_layer_num > layer:
start += element_counts[layer]
end += element_counts[layer]
# Calculate the range of paths in the last layer
current_layer_start = end
current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice
# This uses glDrawRangeElements internally to only draw a certain range of lines.
# All the layers but the current selected layer are rendered first
if self._old_current_path != self._layer_view._current_path_num:
self._current_shader = self._layer_shadow_shader
self._switching_layers = False
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num:
self._current_shader = self._layer_shader
self._switching_layers = True
layers_batch = RenderBatch(self._current_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
layers_batch.addItem(node.getWorldTransformation(), layer_data)
layers_batch.render(self._scene.getActiveCamera())
# Current selected layer is rendered
current_layer_batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (current_layer_start, current_layer_end))
current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
current_layer_batch.render(self._scene.getActiveCamera())
self._old_current_layer = self._layer_view._current_layer_num
self._old_current_path = self._layer_view._current_path_num
# Create a new batch that is not range-limited
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
if self._layer_view.getCurrentLayerMesh():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh())
if self._layer_view.getCurrentLayerJumps():
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps())
if len(batch.items) > 0:
batch.render(self._scene.getActiveCamera())
# The nozzle is drawn when once we know the correct position of the head,
# but the user is not using the layer slider, and the compatibility mode is not enabled
if not self._switching_layers and not self._compatibility_mode and self._layer_view.getActivity() and nozzle_node is not None:
if head_position is not None:
nozzle_node.setVisible(True)
nozzle_node.setPosition(head_position)
nozzle_batch = RenderBatch(self._nozzle_shader, type = RenderBatch.RenderType.Transparent)
nozzle_batch.addItem(nozzle_node.getWorldTransformation(), mesh = nozzle_node.getMeshData())
nozzle_batch.render(self._scene.getActiveCamera())
# Render toolhandles on top of the layerview
if len(tool_handle_batch.items) > 0:
tool_handle_batch.render(self._scene.getActiveCamera())
self.release()

View file

@ -17,6 +17,7 @@ UM.PointingRectangle {
property real value: 0 property real value: 0
property var setValue // Function property var setValue // Function
property bool busy: false property bool busy: false
property int startFrom: 1
target: Qt.point(parent.width, y + height / 2) target: Qt.point(parent.width, y + height / 2)
arrowSize: UM.Theme.getSize("default_arrow").width arrowSize: UM.Theme.getSize("default_arrow").width
@ -48,12 +49,12 @@ UM.PointingRectangle {
anchors { anchors {
left: parent.left left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width / 2 leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
width: 40 * screenScaleFactor width: 40 * screenScaleFactor
text: sliderLabelRoot.value + 1 // the current handle value, add 1 because layers is an array text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
horizontalAlignment: TextInput.AlignRight horizontalAlignment: TextInput.AlignRight
// key bindings, work when label is currenctly focused (active handle in LayerSlider) // key bindings, work when label is currenctly focused (active handle in LayerSlider)
@ -74,14 +75,14 @@ UM.PointingRectangle {
cursorPosition = 0 cursorPosition = 0
if (valueLabel.text != "") { if (valueLabel.text != "") {
// -1 because we need to convert back to an array structure // -startFrom because we need to convert back to an array structure
sliderLabelRoot.setValue(parseInt(valueLabel.text) - 1) sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom)
} }
} }
validator: IntValidator { validator: IntValidator {
bottom: 1 bottom:startFrom
top: sliderLabelRoot.maximumValue + 1 // +1 because actual layers is an array top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0
} }
} }
@ -90,7 +91,7 @@ UM.PointingRectangle {
anchors { anchors {
left: parent.right left: parent.right
leftMargin: UM.Theme.getSize("default_margin").width / 2 leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }

View file

@ -1,46 +1,46 @@
# Copyright (c) 2015 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import sys import sys
from UM.PluginRegistry import PluginRegistry
from UM.View.View import View
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Resources import Resources
from UM.Event import Event, KeyEvent
from UM.Signal import Signal
from UM.Scene.Selection import Selection
from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Job import Job
from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.View.GL.OpenGL import OpenGL
from UM.Message import Message
from UM.Application import Application
from UM.View.GL.OpenGLContext import OpenGLContext
from cura.ConvexHullNode import ConvexHullNode
from cura.Settings.ExtruderManager import ExtruderManager
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from . import LayerViewProxy from UM.Application import Application
from UM.Event import Event, KeyEvent
from UM.Job import Job
from UM.Logger import Logger
from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.Selection import Selection
from UM.Signal import Signal
from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext
from UM.View.View import View
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") from cura.ConvexHullNode import ConvexHullNode
from . import LayerPass from .NozzleNode import NozzleNode
from .SimulationPass import SimulationPass
from .SimulationViewProxy import SimulationViewProxy
catalog = i18nCatalog("cura")
import numpy import numpy
import os.path import os.path
## View used to display g-code paths. ## View used to display g-code paths.
class LayerView(View): class SimulationView(View):
# Must match LayerView.qml # Must match SimulationView.qml
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0 LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
LAYER_VIEW_TYPE_LINE_TYPE = 1 LAYER_VIEW_TYPE_LINE_TYPE = 1
LAYER_VIEW_TYPE_FEEDRATE = 2
LAYER_VIEW_TYPE_THICKNESS = 3
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -54,22 +54,29 @@ class LayerView(View):
self._activity = False self._activity = False
self._old_max_layers = 0 self._old_max_layers = 0
self._max_paths = 0
self._current_path_num = 0
self._minimum_path_num = 0
self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
self._busy = False self._busy = False
self._simulation_running = False
self._ghost_shader = None self._ghost_shader = None
self._layer_pass = None self._layer_pass = None
self._composite_pass = None self._composite_pass = None
self._old_layer_bindings = None self._old_layer_bindings = None
self._layerview_composite_shader = None self._simulationview_composite_shader = None
self._old_composite_shader = None self._old_composite_shader = None
self._global_container_stack = None self._global_container_stack = None
self._proxy = LayerViewProxy.LayerViewProxy() self._proxy = SimulationViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._resetSettings() self._resetSettings()
self._legend_items = None self._legend_items = None
self._show_travel_moves = False self._show_travel_moves = False
self._nozzle_node = None
Preferences.getInstance().addPreference("view/top_layer_count", 5) Preferences.getInstance().addPreference("view/top_layer_count", 5)
Preferences.getInstance().addPreference("view/only_show_top_layers", False) Preferences.getInstance().addPreference("view/only_show_top_layers", False)
@ -91,7 +98,7 @@ class LayerView(View):
self._compatibility_mode = True # for safety self._compatibility_mode = True # for safety
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"), self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
title = catalog.i18nc("@info:title", "Layer View")) title = catalog.i18nc("@info:title", "Simulation View"))
def _resetSettings(self): def _resetSettings(self):
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
@ -101,18 +108,24 @@ class LayerView(View):
self._show_helpers = 1 self._show_helpers = 1
self._show_skin = 1 self._show_skin = 1
self._show_infill = 1 self._show_infill = 1
self.resetLayerData()
def getActivity(self): def getActivity(self):
return self._activity return self._activity
def getLayerPass(self): def setActivity(self, activity):
if self._activity == activity:
return
self._activity = activity
self.activityChanged.emit()
def getSimulationPass(self):
if not self._layer_pass: if not self._layer_pass:
# Currently the RenderPass constructor requires a size > 0 # Currently the RenderPass constructor requires a size > 0
# This should be fixed in RenderPass's constructor. # This should be fixed in RenderPass's constructor.
self._layer_pass = LayerPass.LayerPass(1, 1) self._layer_pass = SimulationPass(1, 1)
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
self._layer_pass.setLayerView(self) self._layer_pass.setSimulationView(self)
self.getRenderer().addRenderPass(self._layer_pass)
return self._layer_pass return self._layer_pass
def getCurrentLayer(self): def getCurrentLayer(self):
@ -121,13 +134,26 @@ class LayerView(View):
def getMinimumLayer(self): def getMinimumLayer(self):
return self._minimum_layer_num return self._minimum_layer_num
def _onSceneChanged(self, node):
self.calculateMaxLayers()
def getMaxLayers(self): def getMaxLayers(self):
return self._max_layers return self._max_layers
busyChanged = Signal() def getCurrentPath(self):
return self._current_path_num
def getMinimumPath(self):
return self._minimum_path_num
def getMaxPaths(self):
return self._max_paths
def getNozzleNode(self):
if not self._nozzle_node:
self._nozzle_node = NozzleNode()
return self._nozzle_node
def _onSceneChanged(self, node):
self.setActivity(False)
self.calculateMaxLayers()
def isBusy(self): def isBusy(self):
return self._busy return self._busy
@ -137,9 +163,19 @@ class LayerView(View):
self._busy = busy self._busy = busy
self.busyChanged.emit() self.busyChanged.emit()
def isSimulationRunning(self):
return self._simulation_running
def setSimulationRunning(self, running):
self._simulation_running = running
def resetLayerData(self): def resetLayerData(self):
self._current_layer_mesh = None self._current_layer_mesh = None
self._current_layer_jumps = None self._current_layer_jumps = None
self._max_feedrate = sys.float_info.min
self._min_feedrate = sys.float_info.max
self._max_thickness = sys.float_info.min
self._min_thickness = sys.float_info.max
def beginRendering(self): def beginRendering(self):
scene = self.getController().getScene() scene = self.getController().getScene()
@ -187,15 +223,43 @@ class LayerView(View):
self.currentLayerNumChanged.emit() self.currentLayerNumChanged.emit()
def setPath(self, value):
if self._current_path_num != value:
self._current_path_num = value
if self._current_path_num < 0:
self._current_path_num = 0
if self._current_path_num > self._max_paths:
self._current_path_num = self._max_paths
if self._current_path_num < self._minimum_path_num:
self._minimum_path_num = self._current_path_num
self._startUpdateTopLayers()
self.currentPathNumChanged.emit()
def setMinimumPath(self, value):
if self._minimum_path_num != value:
self._minimum_path_num = value
if self._minimum_path_num < 0:
self._minimum_path_num = 0
if self._minimum_path_num > self._max_layers:
self._minimum_path_num = self._max_layers
if self._minimum_path_num > self._current_path_num:
self._current_path_num = self._minimum_path_num
self._startUpdateTopLayers()
self.currentPathNumChanged.emit()
## Set the layer view type ## Set the layer view type
# #
# \param layer_view_type integer as in LayerView.qml and this class # \param layer_view_type integer as in SimulationView.qml and this class
def setLayerViewType(self, layer_view_type): def setSimulationViewType(self, layer_view_type):
self._layer_view_type = layer_view_type self._layer_view_type = layer_view_type
self.currentLayerNumChanged.emit() self.currentLayerNumChanged.emit()
## Return the layer view type, integer as in LayerView.qml and this class ## Return the layer view type, integer as in SimulationView.qml and this class
def getLayerViewType(self): def getSimulationViewType(self):
return self._layer_view_type return self._layer_view_type
## Set the extruder opacity ## Set the extruder opacity
@ -244,9 +308,20 @@ class LayerView(View):
def getExtruderCount(self): def getExtruderCount(self):
return self._extruder_count return self._extruder_count
def getMinFeedrate(self):
return self._min_feedrate
def getMaxFeedrate(self):
return self._max_feedrate
def getMinThickness(self):
return self._min_thickness
def getMaxThickness(self):
return self._max_thickness
def calculateMaxLayers(self): def calculateMaxLayers(self):
scene = self.getController().getScene() scene = self.getController().getScene()
self._activity = True
self._old_max_layers = self._max_layers self._old_max_layers = self._max_layers
## Recalculate num max layers ## Recalculate num max layers
@ -256,9 +331,16 @@ class LayerView(View):
if not layer_data: if not layer_data:
continue continue
self.setActivity(True)
min_layer_number = sys.maxsize min_layer_number = sys.maxsize
max_layer_number = -sys.maxsize max_layer_number = -sys.maxsize
for layer_id in layer_data.getLayers(): for layer_id in layer_data.getLayers():
# Store the max and min feedrates and thicknesses for display purposes
for p in layer_data.getLayer(layer_id).polygons:
self._max_feedrate = max(float(p.lineFeedrates.max()), self._max_feedrate)
self._min_feedrate = min(float(p.lineFeedrates.min()), self._min_feedrate)
self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness)
self._min_thickness = min(float(p.lineThicknesses.min()), self._min_thickness)
if max_layer_number < layer_id: if max_layer_number < layer_id:
max_layer_number = layer_id max_layer_number = layer_id
if min_layer_number > layer_id: if min_layer_number > layer_id:
@ -282,10 +364,32 @@ class LayerView(View):
self.maxLayersChanged.emit() self.maxLayersChanged.emit()
self._startUpdateTopLayers() self._startUpdateTopLayers()
def calculateMaxPathsOnLayer(self, layer_num):
# Update the currentPath
scene = self.getController().getScene()
for node in DepthFirstIterator(scene.getRoot()):
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
layer = layer_data.getLayer(layer_num)
if layer is None:
return
new_max_paths = layer.lineMeshElementCount()
if new_max_paths >= 0 and new_max_paths != self._max_paths:
self._max_paths = new_max_paths
self.maxPathsChanged.emit()
self.setPath(int(new_max_paths))
maxLayersChanged = Signal() maxLayersChanged = Signal()
maxPathsChanged = Signal()
currentLayerNumChanged = Signal() currentLayerNumChanged = Signal()
currentPathNumChanged = Signal()
globalStackChanged = Signal() globalStackChanged = Signal()
preferencesChanged = Signal() preferencesChanged = Signal()
busyChanged = Signal()
activityChanged = Signal()
## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
# as this caused some issues. # as this caused some issues.
@ -309,25 +413,31 @@ class LayerView(View):
return True return True
if event.type == Event.ViewActivateEvent: if event.type == Event.ViewActivateEvent:
# Make sure the LayerPass is created # Make sure the SimulationPass is created
self.getLayerPass() layer_pass = self.getSimulationPass()
self.getRenderer().addRenderPass(layer_pass)
# Make sure the NozzleNode is add to the root
nozzle = self.getNozzleNode()
nozzle.setParent(self.getController().getScene().getRoot())
nozzle.setVisible(False)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
if not self._layerview_composite_shader: if not self._simulationview_composite_shader:
self._layerview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layerview_composite.shader")) self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader"))
theme = Application.getInstance().getTheme() theme = Application.getInstance().getTheme()
self._layerview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) self._simulationview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
self._layerview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
if not self._composite_pass: if not self._composite_pass:
self._composite_pass = self.getRenderer().getRenderPass("composite") self._composite_pass = self.getRenderer().getRenderPass("composite")
self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
self._composite_pass.getLayerBindings().append("layerview") self._composite_pass.getLayerBindings().append("simulationview")
self._old_composite_shader = self._composite_pass.getCompositeShader() self._old_composite_shader = self._composite_pass.getCompositeShader()
self._composite_pass.setCompositeShader(self._layerview_composite_shader) self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
elif event.type == Event.ViewDeactivateEvent: elif event.type == Event.ViewDeactivateEvent:
self._wireprint_warning_message.hide() self._wireprint_warning_message.hide()
@ -335,6 +445,8 @@ class LayerView(View):
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
self._nozzle_node.setParent(None)
self.getRenderer().removeRenderPass(self._layer_pass)
self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setLayerBindings(self._old_layer_bindings)
self._composite_pass.setCompositeShader(self._old_composite_shader) self._composite_pass.setCompositeShader(self._old_composite_shader)
@ -363,6 +475,9 @@ class LayerView(View):
else: else:
self._wireprint_warning_message.hide() self._wireprint_warning_message.hide()
def _onCurrentLayerNumChanged(self):
self.calculateMaxPathsOnLayer(self._current_layer_num)
def _startUpdateTopLayers(self): def _startUpdateTopLayers(self):
if not self._compatibility_mode: if not self._compatibility_mode:
return return
@ -396,7 +511,7 @@ class LayerView(View):
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool( self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(
Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
self.setLayerViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type")))); self.setSimulationViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type"))));
for extruder_nr, extruder_opacity in enumerate(Preferences.getInstance().getValue("layerview/extruder_opacities").split("|")): for extruder_nr, extruder_opacity in enumerate(Preferences.getInstance().getValue("layerview/extruder_opacities").split("|")):
try: try:

View file

@ -0,0 +1,707 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item
{
id: base
width: {
if (UM.SimulationView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
} else {
return UM.Theme.getSize("layerview_menu_size").width;
}
}
height: {
if (viewSettings.collapsed) {
if (UM.SimulationView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
}
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
} else if (UM.SimulationView.compatibilityMode) {
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
} else {
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
}
Behavior on height { NumberAnimation { duration: 100 } }
property var buttonTarget: {
if(parent != null)
{
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
}
return Qt.point(0,0)
}
visible: parent != null ? !parent.parent.monitoringPrint: true
Rectangle {
id: layerViewMenu
anchors.right: parent.right
anchors.top: parent.top
width: parent.width
height: parent.height
clip: true
z: layerSlider.z - 1
color: UM.Theme.getColor("tool_panel_background")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Button {
id: collapseButton
anchors.top: parent.top
anchors.topMargin: Math.floor(UM.Theme.getSize("default_margin").height + (UM.Theme.getSize("layerview_row").height - UM.Theme.getSize("default_margin").height) / 2)
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
onClicked: viewSettings.collapsed = !viewSettings.collapsed
style: ButtonStyle
{
background: UM.RecolorImage
{
width: control.width
height: control.height
sourceSize.width: width
sourceSize.height: width
color: UM.Theme.getColor("setting_control_text")
source: viewSettings.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
}
label: Label{ }
}
}
ColumnLayout {
id: viewSettings
property bool collapsed: false
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
// if we are in compatibility mode, we only show the "line type"
property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1
property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3
property bool show_feedrate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 2
property bool show_thickness_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 3
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("layerview_row_spacing").height
Label
{
id: layerViewTypesLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Color scheme")
font: UM.Theme.getFont("default");
visible: !UM.SimulationView.compatibilityMode
Layout.fillWidth: true
color: UM.Theme.getColor("setting_control_text")
}
ListModel // matches SimulationView.py
{
id: layerViewTypes
}
Component.onCompleted:
{
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Material Color"),
type_id: 0
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Line Type"),
type_id: 1
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Feedrate"),
type_id: 2
})
layerViewTypes.append({
text: catalog.i18nc("@label:listbox", "Layer thickness"),
type_id: 3 // these ids match the switching in the shader
})
}
ComboBox
{
id: layerTypeCombobox
anchors.left: parent.left
Layout.fillWidth: true
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
model: layerViewTypes
visible: !UM.SimulationView.compatibilityMode
style: UM.Theme.styles.combobox
anchors.right: parent.right
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
Component.onCompleted:
{
currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// update visibility of legends
viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
viewSettings.show_gradient = !UM.SimulationView.compatibilityMode && (type_id == 2 || type_id == 3);
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
}
}
Label
{
id: compatibilityModeLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Compatibility Mode")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
visible: UM.SimulationView.compatibilityMode
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
Item
{
height: Math.floor(UM.Theme.getSize("default_margin").width / 2)
width: width
}
Connections {
target: UM.Preferences
onPreferenceChanged:
{
layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
playButton.pauseSimulation();
viewSettings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
viewSettings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
viewSettings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
viewSettings.show_skin = UM.Preferences.getValue("layerview/show_skin");
viewSettings.show_infill = UM.Preferences.getValue("layerview/show_infill");
viewSettings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
viewSettings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
}
}
Repeater {
model: Cura.ExtrudersModel{}
CheckBox {
id: extrudersModelCheckBox
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
onClicked: {
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
}
visible: !UM.SimulationView.compatibilityMode
enabled: index + 1 <= 4
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: extrudersModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: model.color
radius: width / 2
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: !viewSettings.show_legend & !viewSettings.show_gradient
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: model.name
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
anchors.verticalCenter: parent.verticalCenter
anchors.left: extrudersModelCheckBox.left;
anchors.right: extrudersModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
Repeater {
model: ListModel {
id: typesLegendModel
Component.onCompleted:
{
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Travels"),
initialValue: viewSettings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Helpers"),
initialValue: viewSettings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Shell"),
initialValue: viewSettings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Infill"),
initialValue: viewSettings.show_infill,
preference: "layerview/show_infill",
colorId: "layerview_infill"
});
}
}
CheckBox {
id: legendModelCheckBox
checked: model.initialValue
onClicked: {
UM.Preferences.setValue(model.preference, checked);
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: legendModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: viewSettings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
Label
{
text: label
font: UM.Theme.getFont("default")
elide: Text.ElideRight
color: UM.Theme.getColor("setting_control_text")
anchors.verticalCenter: parent.verticalCenter
anchors.left: legendModelCheckBox.left;
anchors.right: legendModelCheckBox.right;
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
}
}
}
CheckBox {
checked: viewSettings.only_show_top_layers
onClicked: {
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
}
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
CheckBox {
checked: viewSettings.top_layer_count == 5
onClicked: {
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
}
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Repeater {
model: ListModel {
id: typesLegendModelNoCheck
Component.onCompleted:
{
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin",
});
typesLegendModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x",
});
}
}
Label {
text: label
visible: viewSettings.show_legend
id: typesLegendModelLabel
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: typesLegendModelLabel.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: viewSettings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
}
}
// Text for the minimum, maximum and units for the feedrates and layer thickness
Item {
id: gradientLegend
visible: viewSettings.show_gradient
width: parent.width
height: UM.Theme.getSize("layerview_row").height
anchors {
topMargin: UM.Theme.getSize("slider_layerview_margin").height
horizontalCenter: parent.horizontalCenter
}
Label {
text: minText()
anchors.left: parent.left
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function minText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","min")
}
}
Label {
text: unitsText()
anchors.horizontalCenter: parent.horizontalCenter
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function unitsText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
return "mm/s"
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
return "mm"
}
}
return ""
}
}
Label {
text: maxText()
anchors.right: parent.right
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function maxText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
}
}
return catalog.i18nc("@label","max")
}
}
}
// Gradient colors for feedrate
Rectangle { // In QML 5.9 can be changed by LinearGradient
// Invert values because then the bar is rotated 90 degrees
id: feedrateGradient
visible: viewSettings.show_feedrate_gradient
anchors.left: parent.right
height: parent.width
width: UM.Theme.getSize("layerview_row").height * 1.5
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient {
GradientStop {
position: 0.000
color: Qt.rgba(1, 0.5, 0, 1)
}
GradientStop {
position: 0.625
color: Qt.rgba(0.375, 0.5, 0, 1)
}
GradientStop {
position: 0.75
color: Qt.rgba(0.25, 1, 0, 1)
}
GradientStop {
position: 1.0
color: Qt.rgba(0, 0, 1, 1)
}
}
}
// Gradient colors for layer thickness
Rectangle { // In QML 5.9 can be changed by LinearGradient
// Invert values because then the bar is rotated 90 degrees
id: thicknessGradient
visible: viewSettings.show_thickness_gradient
anchors.left: parent.right
height: parent.width
width: UM.Theme.getSize("layerview_row").height * 1.5
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient {
GradientStop {
position: 0.000
color: Qt.rgba(1, 0, 0, 1)
}
GradientStop {
position: 0.25
color: Qt.rgba(0.5, 0.5, 0, 1)
}
GradientStop {
position: 0.5
color: Qt.rgba(0, 1, 0, 1)
}
GradientStop {
position: 0.75
color: Qt.rgba(0, 0.5, 0.5, 1)
}
GradientStop {
position: 1.0
color: Qt.rgba(0, 0, 1, 1)
}
}
}
}
}
Item {
id: slidersBox
width: parent.width
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
anchors {
top: parent.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
left: parent.left
}
PathSlider {
id: pathSlider
height: UM.Theme.getSize("slider_handle").width
anchors.left: playButton.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
visible: !UM.SimulationView.compatibilityMode
// custom properties
handleValue: UM.SimulationView.currentPath
maximumValue: UM.SimulationView.numPaths
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
handleColor: UM.Theme.getColor("slider_handle")
handleActiveColor: UM.Theme.getColor("slider_handle_active")
rangeColor: UM.Theme.getColor("slider_groove_fill")
// update values when layer data changes
Connections {
target: UM.SimulationView
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
LayerSlider {
id: layerSlider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("layerview_menu_size").height
anchors {
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
right: parent.right
rightMargin: UM.Theme.getSize("slider_layerview_margin").width
}
// custom properties
upperValue: UM.SimulationView.currentLayer
lowerValue: UM.SimulationView.minimumLayer
maximumValue: UM.SimulationView.numLayers
handleSize: UM.Theme.getSize("slider_handle").width
trackThickness: UM.Theme.getSize("slider_groove").width
trackColor: UM.Theme.getColor("slider_groove")
trackBorderColor: UM.Theme.getColor("slider_groove_border")
upperHandleColor: UM.Theme.getColor("slider_handle")
lowerHandleColor: UM.Theme.getColor("slider_handle")
rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
handleActiveColor: UM.Theme.getColor("slider_handle_active")
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
// update values when layer data changes
Connections {
target: UM.SimulationView
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
// Play simulation button
Button {
id: playButton
iconSource: "./resources/simulation_resume.svg"
style: UM.Theme.styles.small_tool_button
visible: !UM.SimulationView.compatibilityMode
anchors {
verticalCenter: pathSlider.verticalCenter
}
property var status: 0 // indicates if it's stopped (0) or playing (1)
onClicked: {
switch(status) {
case 0: {
resumeSimulation()
break
}
case 1: {
pauseSimulation()
break
}
}
}
function pauseSimulation() {
UM.SimulationView.setSimulationRunning(false)
iconSource = "./resources/simulation_resume.svg"
simulationTimer.stop()
status = 0
}
function resumeSimulation() {
UM.SimulationView.setSimulationRunning(true)
iconSource = "./resources/simulation_pause.svg"
simulationTimer.start()
}
}
Timer
{
id: simulationTimer
interval: 100
running: false
repeat: true
onTriggered: {
var currentPath = UM.SimulationView.currentPath
var numPaths = UM.SimulationView.numPaths
var currentLayer = UM.SimulationView.currentLayer
var numLayers = UM.SimulationView.numLayers
// When the user plays the simulation, if the path slider is at the end of this layer, we start
// the simulation at the beginning of the current layer.
if (playButton.status == 0)
{
if (currentPath >= numPaths)
{
UM.SimulationView.setCurrentPath(0)
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
// If the simulation is already playing and we reach the end of a layer, then it automatically
// starts at the beginning of the next layer.
else
{
if (currentPath >= numPaths)
{
// At the end of the model, the simulation stops
if (currentLayer >= numLayers)
{
playButton.pauseSimulation()
}
else
{
UM.SimulationView.setCurrentLayer(currentLayer+1)
UM.SimulationView.setCurrentPath(0)
}
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
playButton.status = 1
}
}
}
FontMetrics {
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

@ -0,0 +1,259 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
import SimulationView
class SimulationViewProxy(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._current_layer = 0
self._controller = Application.getInstance().getController()
self._controller.activeViewChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
self.is_simulationView_selected = False
currentLayerChanged = pyqtSignal()
currentPathChanged = pyqtSignal()
maxLayersChanged = pyqtSignal()
maxPathsChanged = pyqtSignal()
activityChanged = pyqtSignal()
globalStackChanged = pyqtSignal()
preferencesChanged = pyqtSignal()
busyChanged = pyqtSignal()
@pyqtProperty(bool, notify=activityChanged)
def layerActivity(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getActivity()
return False
@pyqtProperty(int, notify=maxLayersChanged)
def numLayers(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxLayers()
return 0
@pyqtProperty(int, notify=currentLayerChanged)
def currentLayer(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getCurrentLayer()
return 0
@pyqtProperty(int, notify=currentLayerChanged)
def minimumLayer(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinimumLayer()
return 0
@pyqtProperty(int, notify=maxPathsChanged)
def numPaths(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxPaths()
return 0
@pyqtProperty(int, notify=currentPathChanged)
def currentPath(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getCurrentPath()
return 0
@pyqtProperty(int, notify=currentPathChanged)
def minimumPath(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinimumPath()
return 0
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.isBusy()
return False
@pyqtProperty(bool, notify=preferencesChanged)
def compatibilityMode(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getCompatibilityMode()
return False
@pyqtSlot(int)
def setCurrentLayer(self, layer_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setLayer(layer_num)
@pyqtSlot(int)
def setMinimumLayer(self, layer_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setMinimumLayer(layer_num)
@pyqtSlot(int)
def setCurrentPath(self, path_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setPath(path_num)
@pyqtSlot(int)
def setMinimumPath(self, path_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setMinimumPath(path_num)
@pyqtSlot(int)
def setSimulationViewType(self, layer_view_type):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setSimulationViewisinstance(layer_view_type)
@pyqtSlot(result=int)
def getSimulationViewType(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getSimulationViewType()
return 0
@pyqtSlot(bool)
def setSimulationRunning(self, running):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setSimulationRunning(running)
@pyqtSlot(result=bool)
def getSimulationRunning(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.isSimulationRunning()
return False
@pyqtSlot(result=float)
def getMinFeedrate(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinFeedrate()
return 0
@pyqtSlot(result=float)
def getMaxFeedrate(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxFeedrate()
return 0
@pyqtSlot(result=float)
def getMinThickness(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinThickness()
return 0
@pyqtSlot(result=float)
def getMaxThickness(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxThickness()
return 0
# Opacity 0..1
@pyqtSlot(int, float)
def setExtruderOpacity(self, extruder_nr, opacity):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setExtruderOpacity(extruder_nr, opacity)
@pyqtSlot(int)
def setShowTravelMoves(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowTravelMoves(show)
@pyqtSlot(int)
def setShowHelpers(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowHelpers(show)
@pyqtSlot(int)
def setShowSkin(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowSkin(show)
@pyqtSlot(int)
def setShowInfill(self, show):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setShowInfill(show)
@pyqtProperty(int, notify=globalStackChanged)
def extruderCount(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getExtruderCount()
return 0
def _layerActivityChanged(self):
self.activityChanged.emit()
def _onLayerChanged(self):
self.currentLayerChanged.emit()
self._layerActivityChanged()
def _onPathChanged(self):
self.currentPathChanged.emit()
self._layerActivityChanged()
def _onMaxLayersChanged(self):
self.maxLayersChanged.emit()
def _onMaxPathsChanged(self):
self.maxPathsChanged.emit()
def _onBusyChanged(self):
self.busyChanged.emit()
def _onActivityChanged(self):
self.activityChanged.emit()
def _onGlobalStackChanged(self):
self.globalStackChanged.emit()
def _onPreferencesChanged(self):
self.preferencesChanged.emit()
def _onActiveViewChanged(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
# remove other connection if once the SimulationView was created.
if self.is_simulationView_selected:
active_view.currentLayerNumChanged.disconnect(self._onLayerChanged)
active_view.currentPathNumChanged.disconnect(self._onPathChanged)
active_view.maxLayersChanged.disconnect(self._onMaxLayersChanged)
active_view.maxPathsChanged.disconnect(self._onMaxPathsChanged)
active_view.busyChanged.disconnect(self._onBusyChanged)
active_view.activityChanged.disconnect(self._onActivityChanged)
active_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
active_view.preferencesChanged.disconnect(self._onPreferencesChanged)
self.is_simulationView_selected = True
active_view.currentLayerNumChanged.connect(self._onLayerChanged)
active_view.currentPathNumChanged.connect(self._onPathChanged)
active_view.maxLayersChanged.connect(self._onMaxLayersChanged)
active_view.maxPathsChanged.connect(self._onMaxPathsChanged)
active_view.busyChanged.connect(self._onBusyChanged)
active_view.activityChanged.connect(self._onActivityChanged)
active_view.globalStackChanged.connect(self._onGlobalStackChanged)
active_view.preferencesChanged.connect(self._onPreferencesChanged)

View file

@ -0,0 +1,26 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtQml import qmlRegisterSingletonType
from UM.i18n import i18nCatalog
from . import SimulationViewProxy, SimulationView
catalog = i18nCatalog("cura")
def getMetaData():
return {
"view": {
"name": catalog.i18nc("@item:inlistbox", "Layer view"),
"view_panel": "SimulationView.qml",
"weight": 2
}
}
def createSimulationViewProxy(engine, script_engine):
return SimulationViewProxy.SimulatorViewProxy()
def register(app):
simulation_view = SimulationView.SimulationView()
qmlRegisterSingletonType(SimulationViewProxy.SimulationViewProxy, "UM", 1, 0, "SimulationView", simulation_view.getProxy)
return { "view": SimulationView.SimulationView()}

View file

@ -6,6 +6,10 @@ vertex41core =
uniform highp mat4 u_modelMatrix; uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix; uniform highp mat4 u_viewProjectionMatrix;
uniform lowp float u_active_extruder; uniform lowp float u_active_extruder;
uniform lowp float u_max_feedrate;
uniform lowp float u_min_feedrate;
uniform lowp float u_max_thickness;
uniform lowp float u_min_thickness;
uniform lowp int u_layer_view_type; uniform lowp int u_layer_view_type;
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
@ -18,6 +22,8 @@ vertex41core =
in highp vec2 a_line_dim; // line width and thickness in highp vec2 a_line_dim; // line width and thickness
in highp float a_extruder; in highp float a_extruder;
in highp float a_line_type; in highp float a_line_type;
in highp float a_feedrate;
in highp float a_thickness;
out lowp vec4 v_color; out lowp vec4 v_color;
@ -32,6 +38,28 @@ vertex41core =
out highp vec3 f_vertex; out highp vec3 f_vertex;
out highp vec3 f_normal; out highp vec3 f_normal;
vec4 feedrateGradientColor(float abs_value, float min_value, float max_value)
{
float value = (abs_value - min_value)/(max_value - min_value);
float red = value;
float green = 1-abs(1-4*value);
if (value > 0.375)
{
green = 0.5;
}
float blue = max(1-4*value, 0);
return vec4(red, green, blue, 1.0);
}
vec4 layerThicknessGradientColor(float abs_value, float min_value, float max_value)
{
float value = (abs_value - min_value)/(max_value - min_value);
float red = max(2*value-1, 0);
float green = 1-abs(1-2*value);
float blue = max(1-2*value, 0);
return vec4(red, green, blue, 1.0);
}
void main() void main()
{ {
vec4 v1_vertex = a_vertex; vec4 v1_vertex = a_vertex;
@ -48,6 +76,12 @@ vertex41core =
case 1: // "Line type" case 1: // "Line type"
v_color = a_color; v_color = a_color;
break; break;
case 2: // "Feedrate"
v_color = feedrateGradientColor(a_feedrate, u_min_feedrate, u_max_feedrate);
break;
case 3: // "Layer thickness"
v_color = layerThicknessGradientColor(a_line_dim.y, u_min_thickness, u_max_thickness);
break;
} }
v_vertex = world_space_vert.xyz; v_vertex = world_space_vert.xyz;
@ -247,6 +281,12 @@ u_show_helpers = 1
u_show_skin = 1 u_show_skin = 1
u_show_infill = 1 u_show_infill = 1
u_min_feedrate = 0
u_max_feedrate = 1
u_min_thickness = 0
u_max_thickness = 1
[bindings] [bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix u_modelMatrix = model_matrix
@ -262,3 +302,5 @@ a_line_dim = line_dim
a_extruder = extruder a_extruder = extruder
a_material_color = material_color a_material_color = material_color
a_line_type = line_type a_line_type = line_type
a_feedrate = feedrate
a_thickness = thickness

View file

@ -0,0 +1,256 @@
[shaders]
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
uniform highp mat4 u_normalMatrix;
in highp vec4 a_vertex;
in lowp vec4 a_color;
in lowp vec4 a_grayColor;
in lowp vec4 a_material_color;
in highp vec4 a_normal;
in highp vec2 a_line_dim; // line width and thickness
in highp float a_extruder;
in highp float a_line_type;
out lowp vec4 v_color;
out highp vec3 v_vertex;
out highp vec3 v_normal;
out lowp vec2 v_line_dim;
out highp int v_extruder;
out highp vec4 v_extruder_opacity;
out float v_line_type;
out lowp vec4 f_color;
out highp vec3 f_vertex;
out highp vec3 f_normal;
void main()
{
vec4 v1_vertex = a_vertex;
v1_vertex.y -= a_line_dim.y / 2; // half layer down
vec4 world_space_vert = u_modelMatrix * v1_vertex;
gl_Position = world_space_vert;
// shade the color depending on the extruder index stored in the alpha component of the color
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
v_vertex = world_space_vert.xyz;
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
v_line_dim = a_line_dim;
v_extruder = int(a_extruder);
v_line_type = a_line_type;
v_extruder_opacity = u_extruder_opacity;
// for testing without geometry shader
f_color = v_color;
f_vertex = v_vertex;
f_normal = v_normal;
}
geometry41core =
#version 410
uniform highp mat4 u_viewProjectionMatrix;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
uniform int u_show_infill;
layout(lines) in;
layout(triangle_strip, max_vertices = 26) out;
in vec4 v_color[];
in vec3 v_vertex[];
in vec3 v_normal[];
in vec2 v_line_dim[];
in int v_extruder[];
in vec4 v_extruder_opacity[];
in float v_line_type[];
out vec4 f_color;
out vec3 f_normal;
out vec3 f_vertex;
// Set the set of variables and EmitVertex
void myEmitVertex(vec3 vertex, vec4 color, vec3 normal, vec4 pos) {
f_vertex = vertex;
f_color = color;
f_normal = normal;
gl_Position = pos;
EmitVertex();
}
void main()
{
vec4 g_vertex_delta;
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
vec3 g_vertex_normal_vert;
vec4 g_vertex_offset_vert;
vec3 g_vertex_normal_horz_head;
vec4 g_vertex_offset_horz_head;
float size_x;
float size_y;
if ((v_extruder_opacity[0][v_extruder[0]] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) {
return;
}
// See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
return;
}
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
return;
}
if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) {
return;
}
if ((u_show_infill == 0) && (v_line_type[0] == 6)) {
return;
}
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
// fixed size for movements
size_x = 0.05;
} else {
size_x = v_line_dim[1].x / 2 + 0.01; // radius, and make it nicely overlapping
}
size_y = v_line_dim[1].y / 2 + 0.01;
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position;
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z));
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0);
g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x));
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz;
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0);
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
// Travels: flat plane with pointy ends
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
EndPrimitive();
} else {
// All normal lines are rendered as 3d tubes.
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
EndPrimitive();
// left side
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
EndPrimitive();
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
EndPrimitive();
// right side
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
EndPrimitive();
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
EndPrimitive();
}
}
fragment41core =
#version 410
in lowp vec4 f_color;
in lowp vec3 f_normal;
in lowp vec3 f_vertex;
out vec4 frag_color;
uniform mediump vec4 u_ambientColor;
uniform highp vec3 u_lightPosition;
void main()
{
mediump vec4 finalColor = vec4(0.0);
float alpha = f_color.a;
finalColor.rgb += f_color.rgb * 0.3;
highp vec3 normal = normalize(f_normal);
highp vec3 light_dir = normalize(u_lightPosition - f_vertex);
// Diffuse Component
highp float NdotL = clamp(dot(normal, light_dir), 0.0, 1.0);
finalColor += (NdotL * f_color);
finalColor.a = alpha; // Do not change alpha in any way
frag_color = finalColor;
}
[defaults]
u_active_extruder = 0.0
u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
u_specularColor = [0.4, 0.4, 0.4, 1.0]
u_ambientColor = [0.3, 0.3, 0.3, 0.0]
u_diffuseColor = [1.0, 0.79, 0.14, 1.0]
u_shininess = 20.0
u_show_travel_moves = 0
u_show_helpers = 1
u_show_skin = 1
u_show_infill = 1
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix
u_viewProjectionMatrix = view_projection_matrix
u_normalMatrix = normal_matrix
u_lightPosition = light_0_position
[attributes]
a_vertex = vertex
a_color = color
a_grayColor = vec4(0.87, 0.12, 0.45, 1.0)
a_normal = normal
a_line_dim = line_dim
a_extruder = extruder
a_material_color = material_color
a_line_type = line_type

View file

@ -0,0 +1,156 @@
[shaders]
vertex =
uniform highp mat4 u_modelViewProjectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
attribute highp float a_extruder;
attribute highp float a_line_type;
attribute highp vec4 a_vertex;
attribute lowp vec4 a_color;
attribute lowp vec4 a_material_color;
varying lowp vec4 v_color;
varying float v_line_type;
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
// shade the color depending on the extruder index
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer;
// 8 and 9 are travel moves
// if ((a_line_type != 8.0) && (a_line_type != 9.0)) {
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
// }
v_line_type = a_line_type;
}
fragment =
varying lowp vec4 v_color;
varying float v_line_type;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
uniform int u_show_infill;
void main()
{
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
// discard movements
discard;
}
// support: 4, 5, 7, 10
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
)) {
discard;
}
// skin: 1, 2, 3
if ((u_show_skin == 0) && (
(v_line_type >= 0.5) && (v_line_type <= 3.5)
)) {
discard;
}
// infill:
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
// discard movements
discard;
}
gl_FragColor = v_color;
}
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
in highp float a_extruder;
in highp float a_line_type;
in highp vec4 a_vertex;
in lowp vec4 a_color;
in lowp vec4 a_material_color;
out lowp vec4 v_color;
out float v_line_type;
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
// if ((a_line_type != 8) && (a_line_type != 9)) {
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
// }
v_line_type = a_line_type;
}
fragment41core =
#version 410
in lowp vec4 v_color;
in float v_line_type;
out vec4 frag_color;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
uniform int u_show_infill;
void main()
{
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
// discard movements
discard;
}
// helpers: 4, 5, 7, 10
if ((u_show_helpers == 0) && (
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
)) {
discard;
}
// skin: 1, 2, 3
if ((u_show_skin == 0) && (
(v_line_type >= 0.5) && (v_line_type <= 3.5)
)) {
discard;
}
// infill:
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
// discard movements
discard;
}
frag_color = v_color;
}
[defaults]
u_active_extruder = 0.0
u_shade_factor = 0.60
u_layer_view_type = 0
u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
u_show_travel_moves = 0
u_show_helpers = 1
u_show_skin = 1
u_show_infill = 1
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
[attributes]
a_vertex = vertex
a_color = color
a_extruder = extruder
a_line_type = line_type
a_material_color = material_color

View file

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

Binary file not shown.

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="6mm"
height="6mm"
viewBox="0 0 5.9999999 6"
version="1.1"
id="svg877"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="simulation_pause2.svg">
<defs
id="defs871" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.839192"
inkscape:cx="-5.3551409"
inkscape:cy="17.386031"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2880"
inkscape:window-height="1675"
inkscape:window-x="-13"
inkscape:window-y="-13"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-11.163774,-122.8006)">
<g
id="g825"
transform="matrix(0.26458333,0,0,0.26458333,10.185689,121.85192)">
<rect
y="5"
x="19"
height="20"
width="2.7552757"
id="rect5192"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.34745646;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
y="5"
x="9"
height="20"
width="2.7552757"
id="rect5192-5"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.34745646;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="6mm"
height="6mm"
viewBox="0 0 6 6"
version="1.1"
id="svg8"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="simulation_resume2.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.839192"
inkscape:cx="-32.404712"
inkscape:cy="14.267522"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2880"
inkscape:window-height="1675"
inkscape:window-x="-13"
inkscape:window-y="-13"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(81.024887,-389.647)">
<path
sodipodi:type="star"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.50520164;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path847"
sodipodi:sides="3"
sodipodi:cx="-78.732257"
sodipodi:cy="392.65222"
sodipodi:r1="3.0592039"
sodipodi:r2="1.5296021"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m -75.67305,392.65222 -4.588806,2.64935 v -5.2987 z"
inkscape:transform-center-x="0.75529536"
inkscape:transform-center-y="0.40090429" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -87,16 +87,11 @@ class SliceInfo(Extension):
data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")} data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")}
# add extruder specific data to slice info
data["extruders"] = [] data["extruders"] = []
extruder_count = len(global_container_stack.extruders)
extruders = []
if extruder_count > 1:
extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position")) extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position"))
if not extruders:
extruders = [global_container_stack]
for extruder in extruders: for extruder in extruders:
extruder_dict = dict() extruder_dict = dict()
extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder

View file

@ -46,21 +46,12 @@ class SolidView(View):
self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*theme.getColor("model_unslicable_alt").getRgb())) self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*theme.getColor("model_unslicable_alt").getRgb()))
self._disabled_shader.setUniformValue("u_width", 50.0) self._disabled_shader.setUniformValue("u_width", 50.0)
multi_extrusion = False
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
if multi_extrusion:
support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value") support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr) support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)
if not support_angle_stack:
support_angle_stack = global_container_stack
else:
support_angle_stack = global_container_stack
if Preferences.getInstance().getValue("view/show_overhang"): if support_angle_stack is not None and Preferences.getInstance().getValue("view/show_overhang"):
angle = support_angle_stack.getProperty("support_angle", "value") angle = support_angle_stack.getProperty("support_angle", "value")
# Make sure the overhang angle is valid before passing it to the shader # Make sure the overhang angle is valid before passing it to the shader
# Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None) # Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)
@ -71,20 +62,12 @@ class SolidView(View):
else: else:
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
for node in DepthFirstIterator(scene.getRoot()): for node in DepthFirstIterator(scene.getRoot()):
if not node.render(renderer): if not node.render(renderer):
if node.getMeshData() and node.isVisible(): if node.getMeshData() and node.isVisible():
uniforms = {} uniforms = {}
shade_factor = 1.0 shade_factor = 1.0
if not multi_extrusion:
if global_container_stack:
material = global_container_stack.findContainer({ "type": "material" })
material_color = material.getMetaDataEntry("color_code", default = self._extruders_model.defaultColors[0]) if material else self._extruders_model.defaultColors[0]
else:
material_color = self._extruders_model.defaultColors[0]
else:
# Get color to render this mesh in from ExtrudersModel # Get color to render this mesh in from ExtrudersModel
extruder_index = 0 extruder_index = 0
extruder_id = node.callDecoration("getActiveExtruder") extruder_id = node.callDecoration("getActiveExtruder")
@ -98,6 +81,7 @@ class SolidView(View):
if extruder_index != ExtruderManager.getInstance().activeExtruderIndex: if extruder_index != ExtruderManager.getInstance().activeExtruderIndex:
# Shade objects that are printed with the non-active extruder 25% darker # Shade objects that are printed with the non-active extruder 25% darker
shade_factor = 0.6 shade_factor = 0.6
try: try:
# Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
# an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])

View file

@ -83,8 +83,7 @@ Component
anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.rightMargin: UM.Theme.getSize("default_margin").width
//TODO; It's probably nicer to do this with a dynamic data model instead of hardcoding this.
//But you know the drill; time constraints don't result in elegant code.
Item Item
{ {
width: parent.width width: parent.width

View file

@ -114,7 +114,7 @@ Cura.MachineAction
Column Column
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
ScrollView ScrollView
@ -191,8 +191,6 @@ Cura.MachineAction
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
//: Tips label
//TODO: get actual link from webteam
text: catalog.i18nc("@label", "If your printer is not listed, read the <a href='%1'>network printing troubleshooting guide</a>").arg("https://ultimaker.com/en/troubleshooting"); text: catalog.i18nc("@label", "If your printer is not listed, read the <a href='%1'>network printing troubleshooting guide</a>").arg("https://ultimaker.com/en/troubleshooting");
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
} }
@ -200,7 +198,7 @@ Cura.MachineAction
} }
Column Column
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
visible: base.selectedPrinter ? true : false visible: base.selectedPrinter ? true : false
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
Label Label
@ -218,13 +216,13 @@ Cura.MachineAction
columns: 2 columns: 2
Label Label
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Type") text: catalog.i18nc("@label", "Type")
} }
Label Label
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: text:
{ {
@ -232,13 +230,13 @@ Cura.MachineAction
{ {
if(base.selectedPrinter.printerType == "ultimaker3") if(base.selectedPrinter.printerType == "ultimaker3")
{ {
return catalog.i18nc("@label", "Ultimaker 3") return catalog.i18nc("@label Printer name", "Ultimaker 3")
} else if(base.selectedPrinter.printerType == "ultimaker3_extended") } else if(base.selectedPrinter.printerType == "ultimaker3_extended")
{ {
return catalog.i18nc("@label", "Ultimaker 3 Extended") return catalog.i18nc("@label Printer name", "Ultimaker 3 Extended")
} else } else
{ {
return catalog.i18nc("@label", "Unknown") // We have no idea what type it is. Should not happen 'in the field' return catalog.i18nc("@label Printer name", "Unknown") // We have no idea what type it is. Should not happen 'in the field'
} }
} }
else else
@ -249,25 +247,25 @@ Cura.MachineAction
} }
Label Label
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Firmware version") text: catalog.i18nc("@label", "Firmware version")
} }
Label Label
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: base.selectedPrinter ? base.selectedPrinter.firmwareVersion : "" text: base.selectedPrinter ? base.selectedPrinter.firmwareVersion : ""
} }
Label Label
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Address") text: catalog.i18nc("@label", "Address")
} }
Label Label
{ {
width: (parent.width * 0.5) | 0 width: Math.floor(parent.width * 0.5)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: base.selectedPrinter ? base.selectedPrinter.ipAddress : "" text: base.selectedPrinter ? base.selectedPrinter.ipAddress : ""
} }
@ -278,12 +276,8 @@ Cura.MachineAction
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text:{ text:{
if (base.selectedPrinter == undefined)
{
return "";
}
// The property cluster size does not exist for older UM3 devices. // The property cluster size does not exist for older UM3 devices.
if(base.selectedPrinter != undefined && base.selectedPrinter.clusterSize == null || base.selectedPrinter.clusterSize == 1) if(!base.selectedPrinter || base.selectedPrinter.clusterSize == null || base.selectedPrinter.clusterSize == 1)
{ {
return ""; return "";
} }

View file

@ -17,10 +17,10 @@ Component
} }
return (sourceSize.width / sourceSize.height) > (maximumWidth / maximumHeight); return (sourceSize.width / sourceSize.height) > (maximumWidth / maximumHeight);
} }
property real _width: Math.min(maximumWidth, sourceSize.width) property real _width: Math.floor(Math.min(maximumWidth, sourceSize.width))
property real _height: Math.min(maximumHeight, sourceSize.height) property real _height: Math.floor(Math.min(maximumHeight, sourceSize.height))
width: proportionalHeight ? _width : sourceSize.width * _height / sourceSize.height width: proportionalHeight ? _width : Math.floor(sourceSize.width * _height / sourceSize.height)
height: !proportionalHeight ? _height : sourceSize.height * _width / sourceSize.width height: !proportionalHeight ? _height : Math.floor(sourceSize.height * _width / sourceSize.width)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
onVisibleChanged: onVisibleChanged:

View file

@ -103,6 +103,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
self._can_pause = True self._can_pause = True
self._can_abort = True self._can_abort = True
self._can_pre_heat_bed = False self._can_pre_heat_bed = False
self._can_control_manually = False
self._cluster_size = int(properties.get(b"cluster_size", 0)) self._cluster_size = int(properties.get(b"cluster_size", 0))
self._cleanupRequest() self._cleanupRequest()
@ -220,7 +221,9 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
self.setPrinters(json_data) self.setPrinters(json_data)
def materialHotendChangedMessage(self, callback): def materialHotendChangedMessage(self, callback):
pass # Do nothing. # When there is just one printer, the activate configuration option is enabled
if (self._cluster_size == 1):
super().materialHotendChangedMessage(callback = callback)
def _startCameraStream(self): def _startCameraStream(self):
## Request new image ## Request new image
@ -483,7 +486,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"]) printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"])
if printer_name is None: if printer_name is None:
printer_name = i18n_catalog.i18nc("@label", "Unknown") printer_name = i18n_catalog.i18nc("@label Printer name", "Unknown")
message_text = (i18n_catalog.i18nc("@info:status", message_text = (i18n_catalog.i18nc("@info:status",
"Printer '{printer_name}' has finished printing '{job_name}'.") "Printer '{printer_name}' has finished printing '{job_name}'.")

View file

@ -102,6 +102,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._target_bed_temperature = 0 self._target_bed_temperature = 0
self._processing_preheat_requests = True self._processing_preheat_requests = True
self._can_control_manually = False
self.setPriority(3) # Make sure the output device gets selected above local file output self.setPriority(3) # Make sure the output device gets selected above local file output
self.setName(key) self.setName(key)
self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")) self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network"))

View file

@ -10,7 +10,7 @@ Item
id: extruderInfo id: extruderInfo
property var printCoreConfiguration property var printCoreConfiguration
width: parent.width / 2 width: Math.floor(parent.width / 2)
height: childrenRect.height height: childrenRect.height
Label Label
{ {

View file

@ -44,7 +44,7 @@ Rectangle
case "maintenance": // TODO: new string case "maintenance": // TODO: new string
case "unknown": case "unknown":
default: default:
return catalog.i18nc("@label", "Unknown"); return catalog.i18nc("@label Printer status", "Unknown");
} }
} }
@ -86,7 +86,7 @@ Rectangle
Rectangle Rectangle
{ {
width: parent.width / 3 width: Math.floor(parent.width / 3)
height: parent.height height: parent.height
Label // Print job name Label // Print job name
@ -131,7 +131,7 @@ Rectangle
Rectangle Rectangle
{ {
width: parent.width / 3 * 2 width: Math.floor(parent.width / 3 * 2)
height: parent.height height: parent.height
Label // Friendly machine name Label // Friendly machine name
@ -139,7 +139,7 @@ Rectangle
id: printerNameLabel id: printerNameLabel
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
width: parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width)
text: printer.friendly_name text: printer.friendly_name
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
elide: Text.ElideRight elide: Text.ElideRight
@ -149,7 +149,7 @@ Rectangle
{ {
id: printerTypeLabel id: printerTypeLabel
anchors.top: printerNameLabel.bottom anchors.top: printerNameLabel.bottom
width: parent.width / 2 - UM.Theme.getSize("default_margin").width width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
text: printer.machine_variant text: printer.machine_variant
anchors.left: parent.left anchors.left: parent.left
elide: Text.ElideRight elide: Text.ElideRight
@ -183,7 +183,7 @@ Rectangle
id: extruderInfo id: extruderInfo
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
width: parent.width / 2 - UM.Theme.getSize("default_margin").width width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
height: childrenRect.height height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width spacing: UM.Theme.getSize("default_margin").width
@ -217,7 +217,7 @@ Rectangle
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
height: showExtended ? parent.height: printProgressTitleBar.height height: showExtended ? parent.height: printProgressTitleBar.height
width: parent.width / 2 - UM.Theme.getSize("default_margin").width width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
border.color: lineColor border.color: lineColor
radius: cornerRadius radius: cornerRadius

View file

@ -57,7 +57,7 @@ Item
{ {
id: cameraImage id: cameraImage
width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth) width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
height: (sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width height: Math.floor((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
z: 1 z: 1

View file

@ -115,8 +115,22 @@ Item
{ {
tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura")
text: catalog.i18nc("@action:button", "Activate Configuration") text: catalog.i18nc("@action:button", "Activate Configuration")
visible: printerConnected visible: printerConnected && !isClusterPrinter()
onClicked: manager.loadConfigurationFromPrinter() onClicked: manager.loadConfigurationFromPrinter()
function isClusterPrinter() {
if(Cura.MachineManager.printerOutputDevices.length == 0)
{
return false;
}
var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize;
// This is not a cluster printer or the cluster it is just one printer
if(clusterSize == undefined || clusterSize == 1)
{
return false;
}
return true;
}
} }
} }

View file

@ -91,12 +91,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
# This will create the view if its not already created. # This will create the view if its not already created.
def spawnFirmwareInterface(self, serial_port): def spawnFirmwareInterface(self, serial_port):
if self._firmware_view is None: if self._firmware_view is None:
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")) path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
component = QQmlComponent(Application.getInstance()._engine, path) self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self})
self._firmware_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._firmware_context.setContextProperty("manager", self)
self._firmware_view = component.create(self._firmware_context)
self._firmware_view.show() self._firmware_view.show()

View file

@ -37,8 +37,7 @@ class UM2UpgradeSelection(MachineAction):
def setHasVariants(self, has_variants = True): def setHasVariants(self, has_variants = True):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
variant_container = global_container_stack.variant variant_container = global_container_stack.extruders["0"].variant
variant_index = global_container_stack.getContainerIndex(variant_container)
if has_variants: if has_variants:
if "has_variants" in global_container_stack.getMetaData(): if "has_variants" in global_container_stack.getMetaData():
@ -52,7 +51,7 @@ class UM2UpgradeSelection(MachineAction):
search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" } search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" }
containers = self._container_registry.findInstanceContainers(**search_criteria) containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers: if containers:
global_container_stack.variant = containers[0] global_container_stack.extruders["0"].variant = containers[0]
else: else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only. # The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
@ -60,6 +59,6 @@ class UM2UpgradeSelection(MachineAction):
global_container_stack.removeMetaDataEntry("has_variants") global_container_stack.removeMetaDataEntry("has_variants")
# Set the variant container to an empty variant # Set the variant container to an empty variant
global_container_stack.variant = ContainerRegistry.getInstance().getEmptyInstanceContainer() global_container_stack.extruders["0"].variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
Application.getInstance().globalContainerStackChanged.emit() Application.getInstance().globalContainerStackChanged.emit()

View file

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

View file

@ -0,0 +1,64 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import UM 1.3 as UM
UM.Dialog
{
id: baseDialog
minimumWidth: Math.floor(UM.Theme.getSize("modal_window_minimum").width * 0.75)
minimumHeight: Math.floor(UM.Theme.getSize("modal_window_minimum").height * 0.5)
width: minimumWidth
height: minimumHeight
title: catalog.i18nc("@title:window", "User Agreement")
TextArea
{
anchors.top: parent.top
width: parent.width
anchors.bottom: buttonRow.top
text: ' <center><h3>DISCLAIMER BY ULTIMAKER</h3></center>
<p>PLEASE READ THIS DISCLAIMER CAREFULLY.</p>
<p>EXCEPT WHEN OTHERWISE STATED IN WRITING, ULTIMAKER PROVIDES ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE AS IS WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF ULTIMAKER SOFTWARE IS WITH YOU.</p>
<p>UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, IN NO EVENT WILL ULTIMAKER BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE.</p>
'
readOnly: true;
textFormat: TextEdit.RichText
}
Item
{
id: buttonRow
anchors.bottom: parent.bottom
width: parent.width
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
UM.I18nCatalog { id: catalog; name:"cura" }
Button
{
anchors.right: parent.right
text: catalog.i18nc("@action:button", "I understand and agree")
onClicked: {
manager.didAgree(true)
}
}
Button
{
anchors.left: parent.left
text: catalog.i18nc("@action:button", "I don't agree")
onClicked: {
manager.didAgree(false)
}
}
}
onClosing: {
manager.didAgree(false)
}
}

View file

@ -0,0 +1,10 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import UserAgreement
def getMetaData():
return {}
def register(app):
return {"extension": UserAgreement.UserAgreement()}

View file

@ -0,0 +1,8 @@
{
"name": "UserAgreement",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Ask the user once if he/she agrees with our license",
"api": 4,
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,227 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To parse preference files.
import io #To serialise the preference files afterwards.
import os
from urllib.parse import quote_plus
from UM.Resources import Resources
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
from cura.CuraApplication import CuraApplication
# a list of all legacy "Not Supported" quality profiles
_OLD_NOT_SUPPORTED_PROFILES = [
"um2p_pp_0.25_normal",
"um2p_tpu_0.8_normal",
"um3_bb0.4_ABS_Fast_Print",
"um3_bb0.4_ABS_Superdraft_Print",
"um3_bb0.4_CPEP_Fast_Print",
"um3_bb0.4_CPEP_Superdraft_Print",
"um3_bb0.4_CPE_Fast_Print",
"um3_bb0.4_CPE_Superdraft_Print",
"um3_bb0.4_Nylon_Fast_Print",
"um3_bb0.4_Nylon_Superdraft_Print",
"um3_bb0.4_PC_Fast_Print",
"um3_bb0.4_PLA_Fast_Print",
"um3_bb0.4_PLA_Superdraft_Print",
"um3_bb0.4_PP_Fast_Print",
"um3_bb0.4_PP_Superdraft_Print",
"um3_bb0.4_TPU_Fast_Print",
"um3_bb0.4_TPU_Superdraft_Print",
"um3_bb0.8_ABS_Fast_Print",
"um3_bb0.8_ABS_Superdraft_Print",
"um3_bb0.8_CPEP_Fast_Print",
"um3_bb0.8_CPEP_Superdraft_Print",
"um3_bb0.8_CPE_Fast_Print",
"um3_bb0.8_CPE_Superdraft_Print",
"um3_bb0.8_Nylon_Fast_Print",
"um3_bb0.8_Nylon_Superdraft_Print",
"um3_bb0.8_PC_Fast_Print",
"um3_bb0.8_PC_Superdraft_Print",
"um3_bb0.8_PLA_Fast_Print",
"um3_bb0.8_PLA_Superdraft_Print",
"um3_bb0.8_PP_Fast_Print",
"um3_bb0.8_PP_Superdraft_Print",
"um3_bb0.8_TPU_Fast_print",
"um3_bb0.8_TPU_Superdraft_Print",
]
# Some containers have their specific empty containers, those need to be set correctly.
_EMPTY_CONTAINER_DICT = {
"1": "empty_quality_changes",
"2": "empty_quality",
"3": "empty_material",
"4": "empty_variant",
}
class VersionUpgrade30to31(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 3.0 format.
#
# Since the format may change, this is implemented for the 3.0 format only
# and needs to be included in the version upgrade system rather than
# globally in Uranium.
#
# \param serialised The serialised form of a CFG file.
# \return The version number stored in the CFG file.
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version
## Upgrades a preferences file from version 3.0 to 3.1.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
def upgradePreferences(self, serialised, filename):
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialised)
# Update version numbers
if "general" not in parser:
parser["general"] = {}
parser["general"]["version"] = "5"
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "4"
# Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
## Upgrades the given instance container file from version 3.0 to 3.1.
#
# \param serialised The serialised form of the container file.
# \param filename The name of the file to upgrade.
def upgradeInstanceContainer(self, serialised, filename):
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialised)
for each_section in ("general", "metadata"):
if not parser.has_section(each_section):
parser.add_section(each_section)
# Copy global quality changes to extruder quality changes for single extrusion machines
if parser["metadata"]["type"] == "quality_changes":
all_quality_changes = self._getSingleExtrusionMachineQualityChanges(parser)
# Note that DO NOT!!! use the quality_changes returned from _getSingleExtrusionMachineQualityChanges().
# Those are loaded from the hard drive which are original files that haven't been upgraded yet.
# NOTE 2: The number can be 0 or 1 depends on whether you are loading it from the qualities folder or
# from a project file. When you load from a project file, the custom profile may not be in cura
# yet, so you will get 0.
if len(all_quality_changes) <= 1 and not parser.has_option("metadata", "extruder"):
self._createExtruderQualityChangesForSingleExtrusionMachine(filename, parser)
# Update version numbers
parser["general"]["version"] = "2"
parser["metadata"]["setting_version"] = "4"
# Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
## Upgrades a container stack from version 3.0 to 3.1.
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
def upgradeStack(self, serialised, filename):
parser = configparser.ConfigParser(interpolation=None)
parser.read_string(serialised)
for each_section in ("general", "metadata"):
if not parser.has_section(each_section):
parser.add_section(each_section)
# change "not supported" quality profiles to empty because they no longer exist
if parser.has_section("containers"):
if parser.has_option("containers", "2"):
quality_profile_id = parser["containers"]["2"]
if quality_profile_id in _OLD_NOT_SUPPORTED_PROFILES:
parser["containers"]["2"] = "empty_quality"
# fix empty containers
for key, specific_empty_container in _EMPTY_CONTAINER_DICT.items():
if parser.has_option("containers", key) and parser["containers"][key] == "empty":
parser["containers"][key] = specific_empty_container
# Update version numbers
if "general" not in parser:
parser["general"] = {}
parser["general"]["version"] = "3"
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "4"
# Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
def _getSingleExtrusionMachineQualityChanges(self, quality_changes_container):
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
quality_changes_containers = []
for item in os.listdir(quality_changes_dir):
file_path = os.path.join(quality_changes_dir, item)
if not os.path.isfile(file_path):
continue
parser = configparser.ConfigParser()
try:
parser.read([file_path])
except:
# skip, it is not a valid stack file
continue
if not parser.has_option("metadata", "type"):
continue
if "quality_changes" != parser["metadata"]["type"]:
continue
if not parser.has_option("general", "name"):
continue
if quality_changes_container["general"]["name"] != parser["general"]["name"]:
continue
quality_changes_containers.append(parser)
return quality_changes_containers
def _createExtruderQualityChangesForSingleExtrusionMachine(self, filename, global_quality_changes):
suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower())
machine_name = os.path.os.path.basename(filename).replace(".inst.cfg", "").replace(suffix, "")
new_filename = machine_name + "_" + "fdmextruder" + suffix
extruder_quality_changes_parser = configparser.ConfigParser()
extruder_quality_changes_parser.add_section("general")
extruder_quality_changes_parser["general"]["version"] = str(2)
extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"]
extruder_quality_changes_parser["general"]["definition"] = global_quality_changes["general"]["definition"]
extruder_quality_changes_parser.add_section("metadata")
extruder_quality_changes_parser["metadata"]["quality_type"] = global_quality_changes["metadata"]["quality_type"]
extruder_quality_changes_parser["metadata"]["type"] = global_quality_changes["metadata"]["type"]
extruder_quality_changes_parser["metadata"]["setting_version"] = str(4)
extruder_quality_changes_parser["metadata"]["extruder"] = "fdmextruder"
extruder_quality_changes_output = io.StringIO()
extruder_quality_changes_parser.write(extruder_quality_changes_output)
extruder_quality_changes_filename = quote_plus(new_filename) + ".inst.cfg"
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w") as f:
f.write(extruder_quality_changes_output.getvalue())

View file

@ -0,0 +1,56 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import VersionUpgrade30to31
upgrade = VersionUpgrade30to31.VersionUpgrade30to31()
def getMetaData():
return {
"version_upgrade": {
# From To Upgrade function
("preferences", 5000003): ("preferences", 5000004, upgrade.upgradePreferences),
("machine_stack", 3000003): ("machine_stack", 3000004, upgrade.upgradeStack),
("extruder_train", 3000003): ("extruder_train", 3000004, upgrade.upgradeStack),
("quality_changes", 2000003): ("quality_changes", 2000004, upgrade.upgradeInstanceContainer),
("user", 2000003): ("user", 2000004, upgrade.upgradeInstanceContainer),
("quality", 2000003): ("quality", 2000004, upgrade.upgradeInstanceContainer),
("definition_changes", 2000003): ("definition_changes", 2000004, upgrade.upgradeInstanceContainer),
("variant", 2000003): ("variant", 2000004, upgrade.upgradeInstanceContainer)
},
"sources": {
"preferences": {
"get_version": upgrade.getCfgVersion,
"location": {"."}
},
"machine_stack": {
"get_version": upgrade.getCfgVersion,
"location": {"./machine_instances"}
},
"extruder_train": {
"get_version": upgrade.getCfgVersion,
"location": {"./extruders"}
},
"quality_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
},
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
},
"definition_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./definition_changes"}
},
"variant": {
"get_version": upgrade.getCfgVersion,
"location": {"./variants"}
}
}
}
def register(app):
return { "version_upgrade": upgrade }

View file

@ -0,0 +1,8 @@
{
"name": "Version Upgrade 3.0 to 3.1",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
"api": 4,
"i18n-catalog": "cura"
}

View file

@ -56,6 +56,7 @@ class XRayView(View):
# Currently the RenderPass constructor requires a size > 0 # Currently the RenderPass constructor requires a size > 0
# This should be fixed in RenderPass's constructor. # This should be fixed in RenderPass's constructor.
self._xray_pass = XRayPass.XRayPass(1, 1) self._xray_pass = XRayPass.XRayPass(1, 1)
self.getRenderer().addRenderPass(self._xray_pass) self.getRenderer().addRenderPass(self._xray_pass)
if not self._xray_composite_shader: if not self._xray_composite_shader:
@ -74,5 +75,6 @@ class XRayView(View):
self._composite_pass.setCompositeShader(self._xray_composite_shader) self._composite_pass.setCompositeShader(self._xray_composite_shader)
if event.type == Event.ViewDeactivateEvent: if event.type == Event.ViewDeactivateEvent:
self.getRenderer().removeRenderPass(self._xray_pass)
self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setLayerBindings(self._old_layer_bindings)
self._composite_pass.setCompositeShader(self._old_composite_shader) self._composite_pass.setCompositeShader(self._old_composite_shader)

View file

@ -39,7 +39,7 @@ class XmlMaterialProfile(InstanceContainer):
@classmethod @classmethod
def xmlVersionToSettingVersion(cls, xml_version: str) -> int: def xmlVersionToSettingVersion(cls, xml_version: str) -> int:
if xml_version == "1.3": if xml_version == "1.3":
return 3 return CuraApplication.SettingVersion
return 0 #Older than 1.3. return 0 #Older than 1.3.
def getInheritedFiles(self): def getInheritedFiles(self):
@ -399,7 +399,7 @@ class XmlMaterialProfile(InstanceContainer):
def getVersionFromSerialized(cls, serialized: str) -> Optional[int]: def getVersionFromSerialized(cls, serialized: str) -> Optional[int]:
data = ET.fromstring(serialized) data = ET.fromstring(serialized)
version = 1 version = XmlMaterialProfile.Version
# get setting version # get setting version
if "version" in data.attrib: if "version" in data.attrib:
setting_version = cls.xmlVersionToSettingVersion(data.attrib["version"]) setting_version = cls.xmlVersionToSettingVersion(data.attrib["version"])
@ -409,11 +409,11 @@ class XmlMaterialProfile(InstanceContainer):
return version * 1000000 + setting_version return version * 1000000 + setting_version
## Overridden from InstanceContainer ## Overridden from InstanceContainer
def deserialize(self, serialized): def deserialize(self, serialized, file_name = None):
containers_to_add = [] containers_to_add = []
# update the serialized data first # update the serialized data first
from UM.Settings.Interfaces import ContainerInterface from UM.Settings.Interfaces import ContainerInterface
serialized = ContainerInterface.deserialize(self, serialized) serialized = ContainerInterface.deserialize(self, serialized, file_name)
try: try:
data = ET.fromstring(serialized) data = ET.fromstring(serialized)
@ -502,8 +502,6 @@ class XmlMaterialProfile(InstanceContainer):
elif key in self.__unmapped_settings: elif key in self.__unmapped_settings:
if key == "hardware compatible": if key == "hardware compatible":
common_compatibility = self._parseCompatibleValue(entry.text) common_compatibility = self._parseCompatibleValue(entry.text)
else:
Logger.log("d", "Unsupported material setting %s", key)
self._cached_values = common_setting_values # from InstanceContainer ancestor self._cached_values = common_setting_values # from InstanceContainer ancestor
meta_data["compatible"] = common_compatibility meta_data["compatible"] = common_compatibility

View file

@ -5,24 +5,16 @@ import xml.etree.ElementTree as ET
from UM.VersionUpgrade import VersionUpgrade from UM.VersionUpgrade import VersionUpgrade
from cura.CuraApplication import CuraApplication
from .XmlMaterialProfile import XmlMaterialProfile
class XmlMaterialUpgrader(VersionUpgrade): class XmlMaterialUpgrader(VersionUpgrade):
def getXmlVersion(self, serialized): def getXmlVersion(self, serialized):
data = ET.fromstring(serialized) return XmlMaterialProfile.getVersionFromSerialized(serialized)
version = 1
# get setting version
if "version" in data.attrib:
setting_version = self._xmlVersionToSettingVersion(data.attrib["version"])
else:
setting_version = self._xmlVersionToSettingVersion("1.2")
return version * 1000000 + setting_version
def _xmlVersionToSettingVersion(self, xml_version: str) -> int: def _xmlVersionToSettingVersion(self, xml_version: str) -> int:
if xml_version == "1.3": return XmlMaterialProfile.xmlVersionToSettingVersion(xml_version)
return 2
return 0 #Older than 1.3.
def upgradeMaterial(self, serialised, filename): def upgradeMaterial(self, serialised, filename):
data = ET.fromstring(serialised) data = ET.fromstring(serialised)

View file

@ -19,7 +19,7 @@ def getMetaData():
"mimetype": "application/x-ultimaker-material-profile" "mimetype": "application/x-ultimaker-material-profile"
}, },
"version_upgrade": { "version_upgrade": {
("materials", 1000000): ("materials", 1000003, upgrader.upgradeMaterial), ("materials", 1000000): ("materials", 1000004, upgrader.upgradeMaterial),
}, },
"sources": { "sources": {
"materials": { "materials": {

View file

@ -6,10 +6,6 @@
"visible": true, "visible": true,
"author": "rikky", "author": "rikky",
"manufacturer": "101Hero", "manufacturer": "101Hero",
"machine_extruder_trains":
{
"0": "fdmextruder"
},
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "101hero-platform.stl", "platform": "101hero-platform.stl",
"supports_usb_connection": true "supports_usb_connection": true

View file

@ -9,11 +9,7 @@
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"icon": "icon_ultimaker2", "icon": "icon_ultimaker2",
"supports_usb_connection": true, "supports_usb_connection": true,
"platform": "3dator_platform.stl", "platform": "3dator_platform.stl"
"machine_extruder_trains":
{
"0": "fdmextruder"
}
}, },
"overrides": { "overrides": {
@ -28,7 +24,6 @@
"layer_height": { "default_value": 0.2 }, "layer_height": { "default_value": 0.2 },
"speed_print": { "default_value": 50 }, "speed_print": { "default_value": 50 },
"speed_infill": { "default_value": 60 }, "speed_infill": { "default_value": 60 },
"machine_extruder_count": { "default_value": 1 },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"machine_height": { "default_value": 260 }, "machine_height": { "default_value": 260 },

View file

@ -0,0 +1,117 @@
{
"id": "builder_premium_large",
"version": 2,
"name": "Builder Premium Large",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Builder SZ",
"manufacturer": "Builder",
"category": "Other",
"quality_definition": "builder_premium_small",
"file_formats": "text/x-gcode",
"platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117],
"has_machine_quality": true,
"preferred_quality": "*Normal*",
"machine_extruder_trains":
{
"0": "builder_premium_large_rear",
"1": "builder_premium_large_front"
}
},
"overrides": {
"machine_name": { "default_value": "Builder Premium Large" },
"machine_heated_bed": { "default_value": true },
"machine_width": { "default_value": 215 },
"machine_height": { "default_value": 600 },
"machine_depth": { "default_value": 205 },
"material_diameter": { "default_value": 1.75 },
"infill_pattern": {"value": "'triangles'" },
"infill_before_walls": {"value": false },
"default_material_print_temperature": { "value": "215" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_standby_temperature": { "value": "material_print_temperature" },
"switch_extruder_retraction_speeds": {"default_value": 15 },
"switch_extruder_retraction_speed": {"default_value": 15 },
"switch_extruder_prime_speed": {"default_value": 15 },
"switch_extruder_retraction_amount": {"value": 1 },
"speed_travel": { "value": "100" },
"speed_layer_0": { "value": "20" },
"speed_prime_tower": { "value": "speed_topbottom" },
"speed_print": { "value": "40" },
"speed_support": { "value": "speed_wall_0" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_topbottom": { "value": "math.ceil(speed_print * 20 / 35)" },
"speed_wall": { "value": "math.ceil(speed_print * 30 / 40)" },
"speed_wall_0": { "value": "math.ceil(speed_wall * 20 / 25)" },
"speed_wall_x": { "value": "speed_wall" },
"prime_tower_position_x": { "default_value": 175 },
"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 },
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_print": { "value": "3000" },
"acceleration_support": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_support_interface": { "value": "acceleration_topbottom" },
"acceleration_topbottom": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_travel": { "value": "acceleration_print" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "default_value": 10 },
"jerk_enabled": { "value": "True" },
"jerk_layer_0": { "value": "jerk_topbottom" },
"jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_print": { "value": "25" },
"jerk_support": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_support_interface": { "value": "jerk_topbottom" },
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
"wall_thickness": { "value": "1.2" },
"retraction_amount": { "default_value": 3 },
"retraction_speed": { "default_value": 15 },
"retraction_retract_speed": { "default_value": 15 },
"retraction_prime_speed": { "default_value": 15 },
"travel_retract_before_outer_wall": { "default_value": true },
"skin_overlap": { "value": "15" },
"adhesion_type": { "default_value": "skirt" },
"machine_nozzle_heat_up_speed": { "default_value": 2 },
"machine_nozzle_cool_down_speed": { "default_value": 2 },
"machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] },
"gantry_height": { "default_value": 55 },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"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 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E15 ;extrude 15mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\nT0 ;Start with Rear Extruder\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\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
},
"machine_extruder_count": { "default_value": 2 }
}
}

View file

@ -0,0 +1,117 @@
{
"id": "builder_premium_medium",
"version": 2,
"name": "Builder Premium Medium",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Builder SZ",
"manufacturer": "Builder",
"category": "Other",
"quality_definition": "builder_premium_small",
"file_formats": "text/x-gcode",
"platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117],
"has_machine_quality": true,
"preferred_quality": "*Normal*",
"machine_extruder_trains":
{
"0": "builder_premium_medium_rear",
"1": "builder_premium_medium_front"
}
},
"overrides": {
"machine_name": { "default_value": "Builder Premium Medium" },
"machine_heated_bed": { "default_value": true },
"machine_width": { "default_value": 215 },
"machine_height": { "default_value": 400 },
"machine_depth": { "default_value": 205 },
"material_diameter": { "default_value": 1.75 },
"infill_pattern": {"value": "'triangles'" },
"infill_before_walls": {"value": false },
"default_material_print_temperature": { "value": "215" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_standby_temperature": { "value": "material_print_temperature" },
"switch_extruder_retraction_speeds": {"default_value": 15 },
"switch_extruder_retraction_speed": {"default_value": 15 },
"switch_extruder_prime_speed": {"default_value": 15 },
"switch_extruder_retraction_amount": {"value": 1 },
"speed_travel": { "value": "100" },
"speed_layer_0": { "value": "20" },
"speed_prime_tower": { "value": "speed_topbottom" },
"speed_print": { "value": "40" },
"speed_support": { "value": "speed_wall_0" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_topbottom": { "value": "math.ceil(speed_print * 20 / 35)" },
"speed_wall": { "value": "math.ceil(speed_print * 30 / 40)" },
"speed_wall_0": { "value": "math.ceil(speed_wall * 20 / 25)" },
"speed_wall_x": { "value": "speed_wall" },
"prime_tower_position_x": { "default_value": 175 },
"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 },
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_print": { "value": "3000" },
"acceleration_support": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_support_interface": { "value": "acceleration_topbottom" },
"acceleration_topbottom": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_travel": { "value": "acceleration_print" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "default_value": 10 },
"jerk_enabled": { "value": "True" },
"jerk_layer_0": { "value": "jerk_topbottom" },
"jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_print": { "value": "25" },
"jerk_support": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_support_interface": { "value": "jerk_topbottom" },
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
"wall_thickness": { "value": "1.2" },
"retraction_amount": { "default_value": 3 },
"retraction_speed": { "default_value": 15 },
"retraction_retract_speed": { "default_value": 15 },
"retraction_prime_speed": { "default_value": 15 },
"travel_retract_before_outer_wall": { "default_value": true },
"skin_overlap": { "value": "15" },
"adhesion_type": { "default_value": "skirt" },
"machine_nozzle_heat_up_speed": { "default_value": 2 },
"machine_nozzle_cool_down_speed": { "default_value": 2 },
"machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] },
"gantry_height": { "default_value": 55 },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"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 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E15 ;extrude 15mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\nT0 ;Start with Rear Extruder\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\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
},
"machine_extruder_count": { "default_value": 2 }
}
}

View file

@ -0,0 +1,116 @@
{
"id": "builder_premium_small",
"version": 2,
"name": "Builder Premium Small",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Builder SZ",
"manufacturer": "Builder",
"category": "Other",
"file_formats": "text/x-gcode",
"platform": "builder_premium_platform.stl",
"platform_offset": [-126, -36, 117],
"has_machine_quality": true,
"preferred_quality": "*Normal*",
"machine_extruder_trains":
{
"0": "builder_premium_small_rear",
"1": "builder_premium_small_front"
}
},
"overrides": {
"machine_name": { "default_value": "Builder Premium Small" },
"machine_heated_bed": { "default_value": true },
"machine_width": { "default_value": 215 },
"machine_height": { "default_value": 200 },
"machine_depth": { "default_value": 205 },
"material_diameter": { "default_value": 1.75 },
"infill_pattern": {"value": "'triangles'" },
"infill_before_walls": {"value": false },
"default_material_print_temperature": { "value": "215" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_standby_temperature": { "value": "material_print_temperature" },
"switch_extruder_retraction_speeds": {"default_value": 15 },
"switch_extruder_retraction_speed": {"default_value": 15 },
"switch_extruder_prime_speed": {"default_value": 15 },
"switch_extruder_retraction_amount": {"value": 1 },
"speed_travel": { "value": "100" },
"speed_layer_0": { "value": "20" },
"speed_prime_tower": { "value": "speed_topbottom" },
"speed_print": { "value": "40" },
"speed_support": { "value": "speed_wall_0" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_topbottom": { "value": "math.ceil(speed_print * 20 / 35)" },
"speed_wall": { "value": "math.ceil(speed_print * 30 / 40)" },
"speed_wall_0": { "value": "math.ceil(speed_wall * 20 / 25)" },
"speed_wall_x": { "value": "speed_wall" },
"prime_tower_position_x": { "default_value": 175 },
"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 },
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_print": { "value": "3000" },
"acceleration_support": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_support_interface": { "value": "acceleration_topbottom" },
"acceleration_topbottom": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_travel": { "value": "acceleration_print" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_min_layer_time": { "default_value": 10 },
"jerk_enabled": { "value": "True" },
"jerk_layer_0": { "value": "jerk_topbottom" },
"jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_print": { "value": "25" },
"jerk_support": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_support_interface": { "value": "jerk_topbottom" },
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
"wall_thickness": { "value": "1.2" },
"retraction_amount": { "default_value": 3 },
"retraction_speed": { "default_value": 15 },
"retraction_retract_speed": { "default_value": 15 },
"retraction_prime_speed": { "default_value": 15 },
"travel_retract_before_outer_wall": { "default_value": true },
"skin_overlap": { "value": "15" },
"adhesion_type": { "default_value": "skirt" },
"machine_nozzle_heat_up_speed": { "default_value": 2 },
"machine_nozzle_cool_down_speed": { "default_value": 2 },
"machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] },
"gantry_height": { "default_value": 55 },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 },
"machine_max_acceleration_z": { "default_value": 500 },
"machine_acceleration": { "default_value": 1000 },
"machine_max_jerk_xy": { "default_value": 10 },
"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 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E15 ;extrude 15mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\nT0 ;Start with Rear Extruder\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\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
},
"machine_extruder_count": { "default_value": 2 }
}
}

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