mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-18 00:55:59 +08:00
Merge remote-tracking branch 'refs/remotes/Ultimaker/master'
This commit is contained in:
commit
9174406df2
2
.gitignore
vendored
2
.gitignore
vendored
@ -43,6 +43,8 @@ plugins/ProfileFlattener
|
||||
plugins/cura-god-mode-plugin
|
||||
plugins/cura-big-flame-graph
|
||||
plugins/cura-siemensnx-plugin
|
||||
plugins/CuraVariSlicePlugin
|
||||
plugins/CuraLiveScriptingPlugin
|
||||
|
||||
#Build stuff
|
||||
CMakeCache.txt
|
||||
|
173
CHANGES
173
CHANGES
@ -1,173 +0,0 @@
|
||||
Cura 15.06 Beta
|
||||
===============
|
||||
|
||||
This is the *Beta* version of Cura 15.06.
|
||||
|
||||
Cura 15.06 is a new release built from the ground up on a completely new
|
||||
framework called Uranium. This framework has been designed to make it easier to
|
||||
extend Cura with additional functionality as well as provide a cleaner UI.
|
||||
|
||||
Changes since 15.05.95
|
||||
----------------------
|
||||
|
||||
* Fixed: Selection ghost remains visible after deleting an object
|
||||
* Fixed: Window does not show up immediately after starting application on OSX
|
||||
* Fixed: Added display of rotation angle during rotation
|
||||
* Fixed: Object changes position while rotating/scaling
|
||||
* Fixed: Loading improvements in the layer view
|
||||
* Fixed: Added application icons
|
||||
* Fixed: Improved feedback when loading models
|
||||
* Fixed: Eject device on MacOSX now provides proper feedback
|
||||
* Fixed: Make it possible to show retraction settings for UM2
|
||||
* Fixed: Opening the machine preferences page will switch to the first available machine
|
||||
* Fixed: Improved tool handle hit area size
|
||||
* Fixed: Render lines with a thickness based on screen DPI
|
||||
|
||||
Changes since 15.05.94
|
||||
----------------------
|
||||
|
||||
* Added Russian translations
|
||||
* Fixed: Infill not displayed in layer view
|
||||
* Fixed: Cannot select/scale/rotate when first activating the tool and then trying to select a model.
|
||||
* Fixed: Improved font rendering on Windows
|
||||
* Fixed: Help > Show Documentation crashes Cura on Windows
|
||||
* Fixed: "There is no disk in the drive" repeating messages on Windows
|
||||
* Fixed: Retraction settings not visible for Ultimaker2
|
||||
* Fixed: Display rotation angle when rotating an object
|
||||
* Fixed: Time/Quality slider values are properly rounded
|
||||
* Fixed: Improved clarity of buttons and text
|
||||
* Fixed: No indication that anything is happening when loading a model
|
||||
* Fixed: Eject device now works on Windows
|
||||
|
||||
Changes since 15.05.93
|
||||
----------------------
|
||||
|
||||
* Fixed: No shortcuts for moving up/down layers in layer view.
|
||||
* Fixed: Last view layers could not be scrolled through in layer view.
|
||||
* Fixed: Files provided on command line would not actually show up on the build
|
||||
platform.
|
||||
* Fixed: Render a ghost of the selection in Layer view to make the actual object
|
||||
position clear.
|
||||
* Fixed: Showing a menu would clear the selection.
|
||||
* Fixed: Size and scaling factor display for scale tool.
|
||||
* Fixed: Missing background for additional tool controls.
|
||||
* Fixed: Loading message times out when loading large files.
|
||||
* Fixed: Show recent files in the file menu.
|
||||
* Fixed: Windows installer will now install MSVC 2010 redistributable, to
|
||||
prevent issues with missing DLL's.
|
||||
* Fixed: Collapsed/expanded state of setting categories not stored.
|
||||
|
||||
Changes since 15.05.91
|
||||
----------------------
|
||||
|
||||
* There is now a working MacOSX version. Currently it supports OSX 10.7 and
|
||||
higher.
|
||||
* Fixed: Need to deselect before selecting a different object.
|
||||
* Fixed: Object can be moved on Z axis.
|
||||
* Fixed: Error values should be considered invalid values and will not trigger a
|
||||
slice.
|
||||
* Fixed: Text fields used a locale-aware validator while the underlying code did
|
||||
not.
|
||||
* Fixed: Text fields will trigger a slice on text change, not only after focus
|
||||
change/enter press.
|
||||
* Fixed: Rotate Tool snaps to incorrect value.
|
||||
* Fixed: Object Collision would only moved objects to the right.
|
||||
* Fixed: Object Collision would move the selected object when it should not.
|
||||
* Fixed: Camera panning now works correctly instead of doing nothing.
|
||||
* Fixed: Camera would flip around center point at maximum rotation.
|
||||
* Fixed: Build platform grid blocked view from below objects.
|
||||
* Fixed: Viewport on MacOSX with high-DPI screens was only taking 1/4th of the
|
||||
window
|
||||
|
||||
Changes since 15.05.90
|
||||
----------------------
|
||||
|
||||
* Fixed: Additional UI elements for tools and views not loading.
|
||||
* Fixed: Double click needed to change setting dialog page.
|
||||
* Fixed: Context menu entries (reload, center object, etc.) not working.
|
||||
* Fixed: "Open With" or passing files from command line not working.
|
||||
* Fixed: "Reload All" would not reload files.
|
||||
|
||||
In addition, a lot of work has gone into getting a usable Mac OSX version.
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
* Plugin based system
|
||||
The Uranium framework provides us with a plugin-based system
|
||||
that provides additional flexibility when extending Cura. Think
|
||||
of new views, tools, file formats, etc. This is probably the
|
||||
biggest new feature.
|
||||
* Improved UI
|
||||
The UI has received a complete overhaul.
|
||||
* Time-Quality Slider
|
||||
The 4 static quick print profiles have been replaced with
|
||||
a slider that should make it easier to find the right spot
|
||||
between print time and print quality.
|
||||
* More Settings
|
||||
The Advanced mode is now configurable and can show many
|
||||
additional settings that were previously not available, while at
|
||||
the same time not overwhelming new users with too many settings.
|
||||
Custom set of visible settings can be created by the user.
|
||||
* Support for high-DPI screens
|
||||
The refreshed UI has been designed with high-DPI screens in
|
||||
mind which should improve the experience of Cura on such
|
||||
devices.
|
||||
* Improved language support
|
||||
(Not yet available for the Beta release.)
|
||||
* Improved support structure generation
|
||||
The new version of the CuraEngine now features improved
|
||||
support generation algorithms and additional options for support
|
||||
structure generation.
|
||||
* Experimental Feature: Wire Printing
|
||||
Wire Printing has been added as an experimental new feature. It
|
||||
will print objects as a structure of lines. It can be enabled by
|
||||
from Advanced Mode -> Fixes -> Wire Printing.
|
||||
* Undo/Redo
|
||||
It is now possible to undo and redo most scene operations, like
|
||||
moving or rotating objects.
|
||||
|
||||
Features from earlier versions not (yet) in this release
|
||||
--------------------------------------------------------
|
||||
|
||||
* The All-at-once/One-at-a-time toggle is not available.
|
||||
We are working on an improved implementation of this mechanism
|
||||
but it will not be available for this release.
|
||||
* No dual extrusion features are available yet.
|
||||
We are working on a completely new workflow for this but this
|
||||
needs additional time.
|
||||
* “Lay Flat” has been removed.
|
||||
The existing implementation was unfortunately not salvageable.
|
||||
We will be looking into an improved implementation for this
|
||||
feature.
|
||||
* "Split Object Into Parts" has been removed.
|
||||
Due to the same reason as Lay Flat.
|
||||
* Support for AMF and DAE file formats has been removed.
|
||||
Both of these will be implemented as plugins in the future.
|
||||
* Support for directly loading a GCode file is not yet available.
|
||||
This will be implemented as a plugin in the future.
|
||||
* Support for PNG, JPG and other image formats has been removed.
|
||||
These can be supported by a plugin with an improved UI.
|
||||
* Support for loading Minecraft levels has been removed.
|
||||
This can be implemented as a plugin.
|
||||
* Windows XP support has been dropped.
|
||||
Microsoft is no longer supporting xp, so they no longer back
|
||||
port certain features that we require.
|
||||
* X-Ray view is missing.
|
||||
Will be implemented as a (you might have guessed it) plugin.
|
||||
* Fixes: Follow Mesh Surface
|
||||
Has been removed from the engine, the same result can be
|
||||
achieved using no infill or top/bottom layers.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
For an up to date list of all known issues, please see
|
||||
https://github.com/Ultimaker/Cura/issues and
|
||||
https://github.com/Ultimaker/Uranium/issues .
|
||||
|
||||
* Some OBJ files are rendered as black objects due to missing
|
||||
normals.
|
||||
* Disabling plugins does not work correctly yet.
|
||||
* Unicorn occasionally still requires feeding. Do not feed it
|
||||
after midnight.
|
@ -39,7 +39,7 @@ find_package(PythonInterp 3.5.0 REQUIRED)
|
||||
install(DIRECTORY resources
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
|
||||
install(DIRECTORY plugins
|
||||
DESTINATION lib/cura)
|
||||
DESTINATION lib${LIB_SUFFIX}/cura)
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
install(FILES cura_app.py
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
@ -47,16 +47,16 @@ if(NOT APPLE AND NOT WIN32)
|
||||
RENAME cura)
|
||||
if(EXISTS /etc/debian_version)
|
||||
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)
|
||||
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()
|
||||
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)
|
||||
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()
|
||||
install(FILES ${CMAKE_BINARY_DIR}/cura.desktop
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
@ -72,8 +72,8 @@ else()
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
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)
|
||||
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()
|
||||
|
@ -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
|
||||
|
||||
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
|
||||
------------
|
||||
|
||||
|
@ -876,15 +876,6 @@ class BuildVolume(SceneNode):
|
||||
|
||||
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.
|
||||
#
|
||||
# For single extrusion machines, this gets the setting from the global
|
||||
@ -899,44 +890,6 @@ class BuildVolume(SceneNode):
|
||||
all_values[i] = 0
|
||||
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.
|
||||
#
|
||||
# This disallowed radius is to allow for space around the models that is
|
||||
@ -945,6 +898,7 @@ class BuildVolume(SceneNode):
|
||||
def _getEdgeDisallowedSize(self):
|
||||
if not self._global_container_stack:
|
||||
return 0
|
||||
|
||||
container_stack = self._global_container_stack
|
||||
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.
|
||||
|
||||
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":
|
||||
skirt_distance = self._getSettingFromAdhesionExtruder("skirt_gap")
|
||||
skirt_line_count = self._getSettingFromAdhesionExtruder("skirt_line_count")
|
||||
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:
|
||||
skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value")
|
||||
skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value")
|
||||
|
||||
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:
|
||||
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.
|
||||
bed_adhesion_size -= self._getSettingFromAdhesionExtruder("skirt_brim_line_width", "value") * self._getSettingFromAdhesionExtruder("initial_layer_line_width_factor", "value") / 100.0
|
||||
|
||||
# We don't create an additional line for the extruder we're printing the skirt with.
|
||||
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
||||
|
||||
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
|
||||
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")
|
||||
bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
|
||||
|
||||
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
|
||||
#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
|
||||
|
||||
# We don't create an additional line for the extruder we're printing the brim with.
|
||||
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
|
||||
|
||||
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":
|
||||
bed_adhesion_size = 0
|
||||
|
||||
else:
|
||||
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
|
||||
|
||||
support_expansion = 0
|
||||
if self._getSettingFromSupportInfillExtruder("support_offset") and self._global_container_stack.getProperty("support_enable", "value"):
|
||||
support_expansion += self._getSettingFromSupportInfillExtruder("support_offset")
|
||||
support_enabled = self._global_container_stack.getProperty("support_enable", "value")
|
||||
support_offset = self._global_container_stack.getProperty("support_offset", "value")
|
||||
if support_enabled and support_offset:
|
||||
support_expansion += support_offset
|
||||
|
||||
farthest_shield_distance = 0
|
||||
if container_stack.getProperty("draft_shield_enabled", "value"):
|
||||
|
@ -302,24 +302,23 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
self._onChanged()
|
||||
|
||||
## 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")
|
||||
if per_mesh_stack:
|
||||
return per_mesh_stack.getProperty(setting_key, property)
|
||||
|
||||
multi_extrusion = self._global_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
if not multi_extrusion:
|
||||
return self._global_stack.getProperty(setting_key, property)
|
||||
return per_mesh_stack.getProperty(setting_key, prop)
|
||||
|
||||
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")
|
||||
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 = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
return extruder_stack.getProperty(setting_key, property)
|
||||
else: #Limit_to_extruder is set. The global stack handles this then.
|
||||
return self._global_stack.getProperty(setting_key, property)
|
||||
return extruder_stack.getProperty(setting_key, prop)
|
||||
else:
|
||||
# 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.
|
||||
def __isDescendant(self, root, node):
|
||||
|
@ -108,11 +108,11 @@ class CrashHandler:
|
||||
except:
|
||||
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 += catalog.i18nc("@label Platform", "<b>Platform:</b> {platform}<br/>").format(platform = platform.platform())
|
||||
crash_info += catalog.i18nc("@label Qt version", "<b>Qt version:</b> {qt}<br/>").format(qt = QT_VERSION_STR)
|
||||
crash_info += catalog.i18nc("@label PyQt version", "<b>PyQt version:</b> {pyqt}<br/>").format(pyqt = PYQT_VERSION_STR)
|
||||
crash_info += catalog.i18nc("@label OpenGL", "<b>OpenGL:</b> {opengl}<br/>").format(opengl = self._getOpenGLInfo())
|
||||
crash_info = "<b>" + catalog.i18nc("@label Cura version number", "Cura version") + ":</b> " + str(self.cura_version) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label Type of platform", "Platform") + ":</b> " + str(platform.platform()) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label", "Qt version") + ":</b> " + str(QT_VERSION_STR) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label", "PyQt version") + ":</b> " + str(PYQT_VERSION_STR) + "<br/>"
|
||||
crash_info += "<b>" + catalog.i18nc("@label OpenGL version", "OpenGL") + ":</b> " + str(self._getOpenGLInfo()) + "<br/>"
|
||||
label.setText(crash_info)
|
||||
|
||||
layout.addWidget(label)
|
||||
@ -126,13 +126,18 @@ class CrashHandler:
|
||||
return group
|
||||
|
||||
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 += catalog.i18nc("@label OpenGL version", "<li>OpenGL Version: {version}</li>").format(version = OpenGL.getInstance().getOpenGLVersion())
|
||||
info += catalog.i18nc("@label OpenGL vendor", "<li>OpenGL Vendor: {vendor}</li>").format(vendor = OpenGL.getInstance().getGPUVendorName())
|
||||
info += catalog.i18nc("@label OpenGL renderer", "<li>OpenGL Renderer: {renderer}</li>").format(renderer = OpenGL.getInstance().getGPUType())
|
||||
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_instance.getGPUVendorName())
|
||||
info += catalog.i18nc("@label OpenGL renderer", "<li>OpenGL Renderer: {renderer}</li>").format(renderer = opengl_instance.getGPUType())
|
||||
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
|
||||
|
||||
@ -280,5 +285,7 @@ class CrashHandler:
|
||||
Application.getInstance().callLater(self._show)
|
||||
|
||||
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_()
|
||||
os._exit(1)
|
||||
|
@ -126,8 +126,6 @@ class CuraApplication(QtApplication):
|
||||
# Cura will always show the Add Machine Dialog upon start.
|
||||
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
|
||||
|
||||
projectFileLoaded = pyqtSignal(str) # Emitted whenever a project file is loaded
|
||||
|
||||
def __init__(self):
|
||||
# this list of dir names will be used by UM to detect an old cura directory
|
||||
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
|
||||
@ -200,6 +198,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
self._machine_action_manager = MachineActionManager.MachineActionManager()
|
||||
self._machine_manager = None # This is initialized on demand.
|
||||
self._extruder_manager = None
|
||||
self._material_manager = None
|
||||
self._setting_inheritance_manager = None
|
||||
self._simple_mode_settings_manager = None
|
||||
@ -217,7 +216,7 @@ class CuraApplication(QtApplication):
|
||||
"CuraEngineBackend",
|
||||
"UserAgreement",
|
||||
"SolidView",
|
||||
"LayerView",
|
||||
"SimulationView",
|
||||
"STLReader",
|
||||
"SelectionTool",
|
||||
"CameraTool",
|
||||
@ -260,14 +259,17 @@ class CuraApplication(QtApplication):
|
||||
# Since they are empty, they should never be serialized and instead just programmatically created.
|
||||
# We need them to simplify the switching between materials.
|
||||
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||
|
||||
empty_variant_container = copy.deepcopy(empty_container)
|
||||
empty_variant_container._id = "empty_variant"
|
||||
empty_variant_container.addMetaDataEntry("type", "variant")
|
||||
ContainerRegistry.getInstance().addContainer(empty_variant_container)
|
||||
|
||||
empty_material_container = copy.deepcopy(empty_container)
|
||||
empty_material_container._id = "empty_material"
|
||||
empty_material_container.addMetaDataEntry("type", "material")
|
||||
ContainerRegistry.getInstance().addContainer(empty_material_container)
|
||||
|
||||
empty_quality_container = copy.deepcopy(empty_container)
|
||||
empty_quality_container._id = "empty_quality"
|
||||
empty_quality_container.setName("Not Supported")
|
||||
@ -275,6 +277,7 @@ class CuraApplication(QtApplication):
|
||||
empty_quality_container.addMetaDataEntry("type", "quality")
|
||||
empty_quality_container.addMetaDataEntry("supported", False)
|
||||
ContainerRegistry.getInstance().addContainer(empty_quality_container)
|
||||
|
||||
empty_quality_changes_container = copy.deepcopy(empty_container)
|
||||
empty_quality_changes_container._id = "empty_quality_changes"
|
||||
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||
@ -425,7 +428,7 @@ class CuraApplication(QtApplication):
|
||||
def discardOrKeepProfileChangesClosed(self, option):
|
||||
if option == "discard":
|
||||
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()
|
||||
global_stack.getTop().clear()
|
||||
|
||||
@ -433,7 +436,7 @@ class CuraApplication(QtApplication):
|
||||
# before slicing. To ensure that slicer uses right settings values
|
||||
elif option == "keep":
|
||||
global_stack = self.getGlobalContainerStack()
|
||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||
for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
|
||||
user_extruder_container = extruder.getTop()
|
||||
if user_extruder_container:
|
||||
user_extruder_container.update()
|
||||
@ -699,16 +702,13 @@ class CuraApplication(QtApplication):
|
||||
|
||||
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.
|
||||
ExtruderManager.getInstance()
|
||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
|
||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
|
||||
self.getSettingInheritanceManager)
|
||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
|
||||
self.getSimpleModeSettingsManager)
|
||||
|
||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||
|
||||
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
|
||||
self._qml_import_paths.append(Resources.getPath(self.ResourceTypes.QmlFiles))
|
||||
|
||||
@ -719,8 +719,8 @@ class CuraApplication(QtApplication):
|
||||
if run_headless or self._engine.rootObjects:
|
||||
self.closeSplash()
|
||||
|
||||
for file in self.getCommandLineOption("file", []):
|
||||
self._openFile(file)
|
||||
for file_name in self.getCommandLineOption("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.
|
||||
self._openFile(file_name)
|
||||
|
||||
@ -733,6 +733,11 @@ class CuraApplication(QtApplication):
|
||||
self._machine_manager = MachineManager.createMachineManager()
|
||||
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):
|
||||
if self._material_manager is None:
|
||||
self._material_manager = MaterialManager.createMaterialManager()
|
||||
@ -783,7 +788,6 @@ class CuraApplication(QtApplication):
|
||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||
|
||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||
|
||||
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
||||
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
|
||||
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
|
||||
@ -793,15 +797,12 @@ class CuraApplication(QtApplication):
|
||||
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
||||
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
||||
qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel")
|
||||
|
||||
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.
|
||||
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
|
||||
qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions")
|
||||
|
||||
engine.rootContext().setContextProperty("ExtruderManager", ExtruderManager.getInstance())
|
||||
|
||||
for path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.QmlFiles):
|
||||
type_name = os.path.splitext(os.path.basename(path))[0]
|
||||
if type_name in ("Cura", "Actions"):
|
||||
@ -1383,7 +1384,7 @@ class CuraApplication(QtApplication):
|
||||
|
||||
extension = os.path.splitext(filename)[1]
|
||||
if extension.lower() in self._non_sliceable_extensions:
|
||||
self.getController().setActiveView("LayerView")
|
||||
self.getController().setActiveView("SimulationView")
|
||||
view = self.getController().getActiveView()
|
||||
view.resetLayerData()
|
||||
view.setLayer(9999999)
|
||||
|
@ -47,12 +47,12 @@ class Layer:
|
||||
|
||||
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_index_offset = index_offset
|
||||
self._element_count = 0
|
||||
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_index_offset += polygon.lineMeshElementCount()
|
||||
self._element_count += polygon.elementCount
|
||||
|
@ -20,11 +20,11 @@ class LayerDataBuilder(MeshBuilder):
|
||||
if layer not in self._layers:
|
||||
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:
|
||||
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)
|
||||
|
||||
def getLayer(self, layer):
|
||||
@ -64,13 +64,14 @@ class LayerDataBuilder(MeshBuilder):
|
||||
line_dimensions = numpy.empty((vertex_count, 2), numpy.float32)
|
||||
colors = numpy.empty((vertex_count, 4), numpy.float32)
|
||||
indices = numpy.empty((index_count, 2), numpy.int32)
|
||||
feedrates = numpy.empty((vertex_count), numpy.float32)
|
||||
extruders = numpy.empty((vertex_count), numpy.float32)
|
||||
line_types = numpy.empty((vertex_count), numpy.float32)
|
||||
|
||||
vertex_offset = 0
|
||||
index_offset = 0
|
||||
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.addVertices(vertices)
|
||||
@ -107,6 +108,11 @@ class LayerDataBuilder(MeshBuilder):
|
||||
"value": line_types,
|
||||
"opengl_name": "a_line_type",
|
||||
"opengl_type": "float"
|
||||
},
|
||||
"feedrates": {
|
||||
"value": feedrates,
|
||||
"opengl_name": "a_feedrate",
|
||||
"opengl_type": "float"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,8 @@ class LayerPolygon:
|
||||
# \param data new_points
|
||||
# \param line_widths array with line widths
|
||||
# \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._types = line_types
|
||||
for i in range(len(self._types)):
|
||||
@ -37,6 +38,7 @@ class LayerPolygon:
|
||||
self._data = data
|
||||
self._line_widths = line_widths
|
||||
self._line_thicknesses = line_thicknesses
|
||||
self._line_feedrates = line_feedrates
|
||||
|
||||
self._vertex_begin = 0
|
||||
self._vertex_end = 0
|
||||
@ -84,10 +86,11 @@ class LayerPolygon:
|
||||
# \param vertices : vertex numpy array to be filled
|
||||
# \param colors : 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 line_types : vertex 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:
|
||||
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.
|
||||
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, 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
|
||||
|
||||
# Convert type per vertex to type per line
|
||||
@ -167,6 +173,14 @@ class LayerPolygon:
|
||||
def lineWidths(self):
|
||||
return self._line_widths
|
||||
|
||||
@property
|
||||
def lineThicknesses(self):
|
||||
return self._line_thicknesses
|
||||
|
||||
@property
|
||||
def lineFeedrates(self):
|
||||
return self._line_feedrates
|
||||
|
||||
@property
|
||||
def jumpMask(self):
|
||||
return self._jump_mask
|
||||
|
@ -1,12 +1,10 @@
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal, QUrl
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
|
||||
from UM.PluginObject import PluginObject
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Application import Application
|
||||
|
||||
import os
|
||||
@ -26,9 +24,6 @@ class MachineAction(QObject, PluginObject):
|
||||
self._key = key
|
||||
self._label = label
|
||||
self._qml_url = ""
|
||||
|
||||
self._component = None
|
||||
self._context = None
|
||||
self._view = None
|
||||
self._finished = False
|
||||
|
||||
@ -52,7 +47,6 @@ class MachineAction(QObject, PluginObject):
|
||||
# /sa _reset
|
||||
@pyqtSlot()
|
||||
def reset(self):
|
||||
self._component = None
|
||||
self._finished = False
|
||||
self._reset()
|
||||
|
||||
@ -73,18 +67,11 @@ class MachineAction(QObject, PluginObject):
|
||||
|
||||
## Protected helper to create a view object based on provided QML.
|
||||
def _createViewFromQML(self):
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
|
||||
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
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())
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)
|
||||
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def displayItem(self):
|
||||
if not self._component:
|
||||
if not self._view:
|
||||
self._createViewFromQML()
|
||||
|
||||
return self._view
|
@ -67,11 +67,10 @@ class PrintInformation(QObject):
|
||||
self._base_name = ""
|
||||
self._abbr_machine = ""
|
||||
self._job_name = ""
|
||||
self._project_name = ""
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
|
||||
Application.getInstance().fileLoaded.connect(self.setBaseName)
|
||||
Application.getInstance().projectFileLoaded.connect(self.setProjectName)
|
||||
Application.getInstance().workspaceLoaded.connect(self.setProjectName)
|
||||
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
self._active_material_container = None
|
||||
@ -253,11 +252,6 @@ class PrintInformation(QObject):
|
||||
self._job_name = name
|
||||
self.jobNameChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setProjectName(self, name):
|
||||
self._project_name = name
|
||||
self.setJobName(name)
|
||||
|
||||
jobNameChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(str, notify = jobNameChanged)
|
||||
@ -265,11 +259,6 @@ class PrintInformation(QObject):
|
||||
return self._job_name
|
||||
|
||||
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 == "":
|
||||
self._job_name = ""
|
||||
self.jobNameChanged.emit()
|
||||
@ -295,7 +284,11 @@ class PrintInformation(QObject):
|
||||
return self._base_name
|
||||
|
||||
@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
|
||||
name = os.path.basename(base_name)
|
||||
|
||||
@ -303,11 +296,17 @@ class PrintInformation(QObject):
|
||||
# extension. This cuts the extension off if necessary.
|
||||
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
|
||||
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._updateJobName()
|
||||
|
||||
|
||||
## Created an acronymn-like abbreviated machine name from the currently active machine name
|
||||
# Called each time the global stack is switched
|
||||
def _setAbbreviatedMachineName(self):
|
||||
|
@ -3,19 +3,15 @@
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtSignal, QUrl
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot, QObject, QTimer, pyqtSignal
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from enum import IntEnum # For the connection state tracking.
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import signalemitter
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Application import Application
|
||||
|
||||
import os
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
## Printer output device adds extra interface options on top of output device.
|
||||
@ -63,11 +59,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
self._camera_active = False
|
||||
|
||||
self._monitor_view_qml_path = ""
|
||||
self._monitor_component = None
|
||||
self._monitor_item = None
|
||||
|
||||
self._control_view_qml_path = ""
|
||||
self._control_component = None
|
||||
self._control_item = None
|
||||
|
||||
self._qml_context = None
|
||||
@ -155,54 +149,31 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
# Note that we specifically only check if the monitor component is created.
|
||||
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
|
||||
# create the item (and fail) every time.
|
||||
if not self._monitor_component:
|
||||
if not self._monitor_item:
|
||||
self._createMonitorViewFromQML()
|
||||
|
||||
return self._monitor_item
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
def controlItem(self):
|
||||
if not self._control_component:
|
||||
if not self._control_item:
|
||||
self._createControlViewFromQML()
|
||||
|
||||
return self._control_item
|
||||
|
||||
def _createControlViewFromQML(self):
|
||||
if not self._control_view_qml_path:
|
||||
return
|
||||
|
||||
path = QUrl.fromLocalFile(self._control_view_qml_path)
|
||||
|
||||
# Because of garbage collection we need to keep this referenced by python.
|
||||
self._control_component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
# Check if the context was already requested before (Printer output device might have multiple items in the future)
|
||||
if self._qml_context is None:
|
||||
self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._qml_context.setContextProperty("OutputDevice", self)
|
||||
|
||||
self._control_item = self._control_component.create(self._qml_context)
|
||||
if self._control_item is None:
|
||||
Logger.log("e", "QQmlComponent status %s", self._control_component.status())
|
||||
Logger.log("e", "QQmlComponent error string %s", self._control_component.errorString())
|
||||
self._control_item = Application.getInstance().createQmlComponent(self._control_view_qml_path, {
|
||||
"OutputDevice": self
|
||||
})
|
||||
|
||||
def _createMonitorViewFromQML(self):
|
||||
if not self._monitor_view_qml_path:
|
||||
return
|
||||
path = QUrl.fromLocalFile(self._monitor_view_qml_path)
|
||||
|
||||
# Because of garbage collection we need to keep this referenced by python.
|
||||
self._monitor_component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
# Check if the context was already requested before (Printer output device might have multiple items in the future)
|
||||
if self._qml_context is None:
|
||||
self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._qml_context.setContextProperty("OutputDevice", self)
|
||||
|
||||
self._monitor_item = self._monitor_component.create(self._qml_context)
|
||||
if self._monitor_item is None:
|
||||
Logger.log("e", "QQmlComponent status %s", self._monitor_component.status())
|
||||
Logger.log("e", "QQmlComponent error string %s", self._monitor_component.errorString())
|
||||
self._monitor_item = Application.getInstance().createQmlComponent(self._monitor_view_qml_path, {
|
||||
"OutputDevice": self
|
||||
})
|
||||
|
||||
@pyqtProperty(str, notify=printerTypeChanged)
|
||||
def printerType(self):
|
||||
|
@ -183,20 +183,11 @@ class QualityManager:
|
||||
|
||||
materials = []
|
||||
|
||||
# TODO: fix this
|
||||
if extruder_stacks:
|
||||
# Multi-extruder machine detected
|
||||
for stack in extruder_stacks:
|
||||
if stack.getId() == active_stack_id and machine_manager.newMaterial:
|
||||
materials.append(machine_manager.newMaterial)
|
||||
else:
|
||||
materials.append(stack.material)
|
||||
else:
|
||||
# Machine with one extruder
|
||||
if global_container_stack.getId() == active_stack_id and machine_manager.newMaterial:
|
||||
materials.append(machine_manager.newMaterial)
|
||||
else:
|
||||
materials.append(global_container_stack.material)
|
||||
|
||||
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import configparser
|
||||
|
||||
from typing import Optional
|
||||
|
||||
@ -17,8 +18,9 @@ from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
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.Resources import Resources
|
||||
|
||||
from . import ExtruderStack
|
||||
from . import GlobalStack
|
||||
@ -42,12 +44,13 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
# Global stack based on metadata information.
|
||||
@override(ContainerRegistry)
|
||||
def addContainer(self, container):
|
||||
|
||||
# Note: Intentional check with type() because we want to ignore subclasses
|
||||
if type(container) == ContainerStack:
|
||||
container = self._convertContainerStack(container)
|
||||
|
||||
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
|
||||
#Check against setting version of the definition.
|
||||
# Check against setting version of the definition.
|
||||
required_setting_version = CuraApplication.SettingVersion
|
||||
actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
|
||||
if required_setting_version != actual_setting_version:
|
||||
@ -256,7 +259,8 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
@override(ContainerRegistry)
|
||||
def load(self):
|
||||
super().load()
|
||||
self._fixupExtruders()
|
||||
self._registerSingleExtrusionMachinesExtruderStacks()
|
||||
self._connectUpgradedExtruderStacksToMachines()
|
||||
|
||||
## Update an imported profile to match the current machine configuration.
|
||||
#
|
||||
@ -299,10 +303,13 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
|
||||
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
|
||||
del quality_type_criteria["definition"]
|
||||
materials = None
|
||||
|
||||
# materials = None
|
||||
|
||||
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"]
|
||||
|
||||
# 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
|
||||
@ -360,8 +367,8 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
return global_container_stack.material.getId()
|
||||
return ""
|
||||
|
||||
## Returns true if the current machien requires its own quality profiles
|
||||
# \return 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 machine requires its own quality profiles
|
||||
def _machineHasOwnQualities(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
@ -394,12 +401,135 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||
|
||||
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.
|
||||
# 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
|
||||
# set after upgrading, because the proper global stack was not yet loaded. This method
|
||||
# makes sure those extruders also get the right stack set.
|
||||
def _fixupExtruders(self):
|
||||
def _connectUpgradedExtruderStacksToMachines(self):
|
||||
extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack)
|
||||
for extruder_stack in extruder_stacks:
|
||||
if extruder_stack.getNextStack():
|
||||
|
@ -41,9 +41,20 @@ class CuraContainerStack(ContainerStack):
|
||||
def __init__(self, container_id: str, *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[_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)
|
||||
|
||||
@ -110,7 +121,7 @@ class CuraContainerStack(ContainerStack):
|
||||
#
|
||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||
def setQualityById(self, new_quality_id: str) -> None:
|
||||
quality = self._empty_instance_container
|
||||
quality = self._empty_quality
|
||||
if new_quality_id == "default":
|
||||
new_quality = self.findDefaultQuality()
|
||||
if new_quality:
|
||||
@ -148,7 +159,7 @@ class CuraContainerStack(ContainerStack):
|
||||
#
|
||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||
def setMaterialById(self, new_material_id: str) -> None:
|
||||
material = self._empty_instance_container
|
||||
material = self._empty_material
|
||||
if new_material_id == "default":
|
||||
new_material = self.findDefaultMaterial()
|
||||
if new_material:
|
||||
@ -186,7 +197,7 @@ class CuraContainerStack(ContainerStack):
|
||||
#
|
||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||
def setVariantById(self, new_variant_id: str) -> None:
|
||||
variant = self._empty_instance_container
|
||||
variant = self._empty_variant
|
||||
if new_variant_id == "default":
|
||||
new_variant = self.findDefaultVariant()
|
||||
if new_variant:
|
||||
@ -348,8 +359,8 @@ class CuraContainerStack(ContainerStack):
|
||||
#
|
||||
# \throws InvalidContainerStackError Raised when no definition can be found for the stack.
|
||||
@override(ContainerStack)
|
||||
def deserialize(self, contents: str) -> None:
|
||||
super().deserialize(contents)
|
||||
def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
|
||||
super().deserialize(contents, file_name)
|
||||
|
||||
new_containers = self._containers.copy()
|
||||
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.
|
||||
def findDefaultVariant(self) -> Optional[ContainerInterface]:
|
||||
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.
|
||||
return None
|
||||
|
||||
@ -454,7 +467,7 @@ class CuraContainerStack(ContainerStack):
|
||||
else:
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
if self.material != self._empty_instance_container:
|
||||
if self.material != self._empty_material:
|
||||
search_criteria["name"] = self.material.name
|
||||
else:
|
||||
preferred_material = definition.getMetaDataEntry("preferred_material")
|
||||
@ -501,7 +514,7 @@ class CuraContainerStack(ContainerStack):
|
||||
else:
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
if self.quality != self._empty_instance_container:
|
||||
if self.quality != self._empty_quality:
|
||||
search_criteria["name"] = self.quality.name
|
||||
else:
|
||||
preferred_quality = definition.getMetaDataEntry("preferred_quality")
|
||||
|
@ -47,6 +47,24 @@ class CuraStackBuilder:
|
||||
|
||||
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=machine_definition,
|
||||
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):
|
||||
position = extruder_definition.getMetaDataEntry("position", None)
|
||||
if not position:
|
||||
@ -62,6 +80,7 @@ class CuraStackBuilder:
|
||||
variant = "default",
|
||||
next_stack = new_global_stack
|
||||
)
|
||||
new_global_stack.addExtruder(new_extruder)
|
||||
|
||||
return new_global_stack
|
||||
|
||||
@ -79,7 +98,9 @@ class CuraStackBuilder:
|
||||
stack.setName(definition.getName())
|
||||
stack.setDefinition(definition)
|
||||
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"])
|
||||
|
||||
user_container = InstanceContainer(new_stack_id + "_user")
|
||||
|
@ -1,21 +1,18 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt.
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
||||
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.Decorators import deprecated
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID.
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
||||
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.
|
||||
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.
|
||||
extrudersChanged = pyqtSignal(QVariant)
|
||||
|
||||
@ -38,18 +49,6 @@ class ExtruderManager(QObject):
|
||||
## Notify when the user switches the currently active extruder.
|
||||
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.
|
||||
#
|
||||
# The currently active extruder stack is the stack that is currently being
|
||||
@ -76,23 +75,23 @@ class ExtruderManager(QObject):
|
||||
return 0
|
||||
|
||||
## 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)
|
||||
def extruderIds(self):
|
||||
extruder_stack_ids = {}
|
||||
|
||||
global_stack_id = Application.getInstance().getGlobalContainerStack().getId()
|
||||
extruder_stack_ids["-1"] = global_stack_id
|
||||
|
||||
if global_stack_id in self._extruder_trains:
|
||||
for position in self._extruder_trains[global_stack_id]:
|
||||
extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
|
||||
|
||||
return extruder_stack_ids
|
||||
|
||||
@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()]:
|
||||
extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
|
||||
if extruder.getId() == id:
|
||||
if extruder.getId() == extruder_stack_id:
|
||||
return extruder.qualityChanges.getId()
|
||||
|
||||
## 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.
|
||||
__instance = None
|
||||
|
||||
@staticmethod
|
||||
def createExtruderManager():
|
||||
return ExtruderManager().getInstance()
|
||||
|
||||
## Gets an instance of the extruder manager, or creates one if no instance
|
||||
# exists yet.
|
||||
#
|
||||
@ -185,6 +188,7 @@ class ExtruderManager(QObject):
|
||||
if global_container_stack.getId() in self._extruder_trains:
|
||||
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 None
|
||||
|
||||
## Get an extruder stack by index
|
||||
@ -203,40 +207,6 @@ class ExtruderManager(QObject):
|
||||
result.append(self.getExtruderStack(i))
|
||||
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.findContainerStacks(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):
|
||||
changed = False
|
||||
|
||||
@ -256,138 +226,6 @@ class ExtruderManager(QObject):
|
||||
if changed:
|
||||
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 preferred_material.isReadOnly()]
|
||||
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
|
||||
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if not 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)
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
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)
|
||||
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):
|
||||
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 property \type{str} The property to get.
|
||||
# \return \type{List} the list of results
|
||||
def getAllExtruderSettings(self, setting_key, property):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack.getProperty("machine_extruder_count", "value") <= 1:
|
||||
return [global_container_stack.getProperty(setting_key, property)]
|
||||
|
||||
def getAllExtruderSettings(self, setting_key: str, prop: str):
|
||||
result = []
|
||||
for index in self.extruderIds:
|
||||
extruder_stack_id = self.extruderIds[str(index)]
|
||||
stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
result.append(stack.getProperty(setting_key, property))
|
||||
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
result.append(extruder_stack.getProperty(setting_key, prop))
|
||||
return result
|
||||
|
||||
## Gets the extruder stacks that are actually being used at the moment.
|
||||
@ -422,20 +256,25 @@ class ExtruderManager(QObject):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
if global_stack.getProperty("machine_extruder_count", "value") <= 1: #For single extrusion.
|
||||
return [global_stack]
|
||||
|
||||
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_bottom_enabled = False
|
||||
support_roof_enabled = False
|
||||
|
||||
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:
|
||||
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"]
|
||||
used_extruder_stack_ids.add(extruder_stack_id)
|
||||
|
||||
@ -471,9 +310,10 @@ class ExtruderManager(QObject):
|
||||
if support_roof_enabled:
|
||||
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_roof_extruder_nr", "value"))])
|
||||
|
||||
#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":
|
||||
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))])
|
||||
|
||||
try:
|
||||
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.
|
||||
@ -520,10 +360,6 @@ class ExtruderManager(QObject):
|
||||
result = []
|
||||
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:
|
||||
for extruder in sorted(self._extruder_trains[global_stack.getId()]):
|
||||
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.globalContainerStackDefinitionChanged.emit()
|
||||
|
||||
# If the global container changed, the number of extruders could be changed and so the active_extruder_index is updated
|
||||
extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
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()
|
||||
# If the global container changed, the machine changed and might have extruders that were not registered yet
|
||||
self._addCurrentMachineExtruders()
|
||||
|
||||
self.resetSelectedObjectExtruders()
|
||||
|
||||
## Adds the extruders of the currently active machine.
|
||||
def _addCurrentMachineExtruders(self) -> None:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack and global_stack.getBottom():
|
||||
self.addMachineExtruders(global_stack.getBottom(), global_stack.getId())
|
||||
extruders_changed = False
|
||||
|
||||
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.
|
||||
#
|
||||
@ -632,7 +483,7 @@ class ExtruderManager(QObject):
|
||||
#
|
||||
# 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
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
@ -656,7 +507,8 @@ class ExtruderManager(QObject):
|
||||
value = extruder.getRawProperty(key, "value")
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder)
|
||||
else: #Just a value from global.
|
||||
else:
|
||||
# Just a value from global.
|
||||
value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value")
|
||||
|
||||
return value
|
||||
|
@ -92,8 +92,8 @@ class ExtruderStack(CuraContainerStack):
|
||||
return self.getNextStack()._getMachineDefinition()
|
||||
|
||||
@override(CuraContainerStack)
|
||||
def deserialize(self, contents: str) -> None:
|
||||
super().deserialize(contents)
|
||||
def deserialize(self, contents: str, file_name: Optional[str] = None) -> None:
|
||||
super().deserialize(contents, file_name)
|
||||
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
|
||||
if stacks:
|
||||
self.setNextStack(stacks[0])
|
||||
@ -115,6 +115,11 @@ class ExtruderStack(CuraContainerStack):
|
||||
if has_global_dependencies:
|
||||
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(
|
||||
name = "application/x-cura-extruderstack",
|
||||
|
@ -74,10 +74,10 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
self._active_machine_extruders = [] # type: Iterable[ExtruderStack]
|
||||
self._add_optional_extruder = False
|
||||
|
||||
#Listen to changes.
|
||||
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.
|
||||
self._extrudersChanged() #Also calls _updateExtruders.
|
||||
# Listen to changes
|
||||
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
|
||||
self._extrudersChanged() # Also calls _updateExtruders
|
||||
|
||||
def setAddGlobal(self, add):
|
||||
if add != self._add_global:
|
||||
@ -128,21 +128,24 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
def _extrudersChanged(self, machine_id = None):
|
||||
if machine_id is not 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():
|
||||
return #Not the current machine.
|
||||
#Unlink from old extruders.
|
||||
# Not the current machine
|
||||
return
|
||||
|
||||
# Unlink from old extruders
|
||||
for extruder in self._active_machine_extruders:
|
||||
extruder.containersChanged.disconnect(self._onExtruderStackContainersChanged)
|
||||
|
||||
#Link to new extruders.
|
||||
# Link to new extruders
|
||||
self._active_machine_extruders = []
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
for extruder in extruder_manager.getExtruderStacks():
|
||||
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||
self._active_machine_extruders.append(extruder)
|
||||
|
||||
self._updateExtruders() #Since the new extruders may have different properties, update our own model.
|
||||
self._updateExtruders() # Since the new extruders may have different properties, update our own model.
|
||||
|
||||
def _onExtruderStackContainersChanged(self, container):
|
||||
# Update when there is an empty container or material change
|
||||
@ -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
|
||||
self._updateExtruders()
|
||||
|
||||
|
||||
modelChanged = pyqtSignal()
|
||||
|
||||
def _updateExtruders(self):
|
||||
@ -161,14 +163,17 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
# This should be called whenever the list of extruders changes.
|
||||
@UM.FlameProfiler.profile
|
||||
def __updateExtruders(self):
|
||||
changed = False
|
||||
extruders_changed = False
|
||||
|
||||
if self.rowCount() != 0:
|
||||
changed = True
|
||||
extruders_changed = True
|
||||
|
||||
items = []
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
|
||||
# TODO: remove this - CURA-4482
|
||||
if self._add_global:
|
||||
material = global_container_stack.material
|
||||
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": ""
|
||||
}
|
||||
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")
|
||||
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
|
||||
try:
|
||||
position = int(position)
|
||||
except ValueError: #Not a proper int.
|
||||
except ValueError:
|
||||
# Not a proper int.
|
||||
position = -1
|
||||
if position >= machine_extruder_count:
|
||||
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]
|
||||
color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color
|
||||
item = { #Construct an item with only the relevant information.
|
||||
default_color = self.defaultColors[position] if 0 <= position < len(self.defaultColors) else self.defaultColors[0]
|
||||
color = extruder.material.getMetaDataEntry("color_code", default = default_color) if extruder.material else default_color
|
||||
|
||||
# construct an item with only the relevant information
|
||||
item = {
|
||||
"id": extruder.getId(),
|
||||
"name": extruder_name,
|
||||
"name": extruder.getName(),
|
||||
"color": color,
|
||||
"index": position,
|
||||
"definition": extruder.getBottom().getId(),
|
||||
"material": material.getName() if material else "",
|
||||
"variant": variant.getName() if variant else "",
|
||||
"material": extruder.material.getName() if extruder.material 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"])
|
||||
|
||||
# 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:
|
||||
item = {
|
||||
"id": "",
|
||||
@ -223,5 +232,6 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||
"definition": ""
|
||||
}
|
||||
items.append(item)
|
||||
|
||||
self.setItems(items)
|
||||
self.modelChanged.emit()
|
||||
|
@ -25,7 +25,7 @@ class GlobalStack(CuraContainerStack):
|
||||
|
||||
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
|
||||
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
|
||||
@ -61,13 +61,6 @@ class GlobalStack(CuraContainerStack):
|
||||
# \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we
|
||||
# already have the maximum number of extruders.
|
||||
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")
|
||||
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)
|
||||
|
@ -107,7 +107,6 @@ class MachineManager(QObject):
|
||||
if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacks(id = active_machine_id):
|
||||
# An active machine was saved, so restore it.
|
||||
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
|
||||
ExtruderManager.getInstance().setActiveExtruderIndex(0)
|
||||
|
||||
@ -162,7 +161,7 @@ class MachineManager(QObject):
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def totalNumberOfSettings(self) -> int:
|
||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys())
|
||||
|
||||
def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
|
||||
if not self._global_container_stack:
|
||||
@ -258,13 +257,13 @@ class MachineManager(QObject):
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
self._auto_hotends_changed = {} #Processed all of them now.
|
||||
self._auto_hotends_changed = {} # Processed all of them now.
|
||||
|
||||
def _onGlobalContainerChanged(self):
|
||||
if self._global_container_stack:
|
||||
try:
|
||||
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
|
||||
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected.
|
||||
except TypeError: # pyQtSignal gives a TypeError when disconnecting from something that was already disconnected.
|
||||
pass
|
||||
try:
|
||||
self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
||||
@ -274,53 +273,39 @@ class MachineManager(QObject):
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
except TypeError:
|
||||
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():
|
||||
extruder_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
extruder_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
||||
|
||||
# update the local global container stack reference
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
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:
|
||||
Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId())
|
||||
|
||||
self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
|
||||
self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
|
||||
self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
|
||||
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
# 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.
|
||||
# set the global variant to empty as we now use the extruder stack at all times - CURA-4482
|
||||
global_variant = self._global_container_stack.variant
|
||||
if global_variant != 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
|
||||
if global_material != 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.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()
|
||||
|
||||
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
|
||||
@ -336,8 +321,6 @@ class MachineManager(QObject):
|
||||
old_active_container_stack = self._active_container_stack
|
||||
|
||||
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()
|
||||
|
||||
@ -384,15 +367,6 @@ class MachineManager(QObject):
|
||||
else:
|
||||
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:
|
||||
if self._global_container_stack is None: #No active machine.
|
||||
return False
|
||||
@ -646,12 +620,17 @@ class MachineManager(QObject):
|
||||
@pyqtProperty(str, notify=activeQualityChanged)
|
||||
def activeQualityId(self) -> str:
|
||||
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
|
||||
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()
|
||||
else:
|
||||
# Custom profile
|
||||
return quality_changes.getId()
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify=activeQualityChanged)
|
||||
@ -716,9 +695,9 @@ class MachineManager(QObject):
|
||||
@pyqtProperty(str, notify = activeQualityChanged)
|
||||
def activeQualityChangesId(self) -> str:
|
||||
if self._active_container_stack:
|
||||
changes = self._active_container_stack.qualityChanges
|
||||
if changes and changes.getId() != "empty":
|
||||
return changes.getId()
|
||||
quality_changes = self._active_container_stack.qualityChanges
|
||||
if quality_changes and not isinstance(quality_changes, type(self._empty_quality_changes_container)):
|
||||
return quality_changes.getId()
|
||||
return ""
|
||||
|
||||
## Check if a container is read_only
|
||||
@ -732,15 +711,13 @@ class MachineManager(QObject):
|
||||
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
|
||||
@pyqtSlot(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")
|
||||
stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
||||
stacks.append(self._global_container_stack)
|
||||
for extruder_stack in stacks:
|
||||
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
||||
|
||||
# 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:
|
||||
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
|
||||
# Depending on from/to material+current variant, a quality profile is chosen and set.
|
||||
@ -786,13 +763,14 @@ class MachineManager(QObject):
|
||||
quality_type = old_quality_changes.getMetaDataEntry("quality_type")
|
||||
new_quality_id = old_quality_changes.getId()
|
||||
|
||||
# See if the requested quality type is available in the new situation.
|
||||
machine_definition = self._active_container_stack.getBottom()
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack:
|
||||
quality_manager = QualityManager.getInstance()
|
||||
|
||||
candidate_quality = None
|
||||
if quality_type:
|
||||
candidate_quality = quality_manager.findQualityByQualityType(quality_type,
|
||||
quality_manager.getWholeMachineDefinition(machine_definition),
|
||||
quality_manager.getWholeMachineDefinition(global_stack.definition),
|
||||
[material_container])
|
||||
|
||||
if not candidate_quality or isinstance(candidate_quality, type(self._empty_quality_changes_container)):
|
||||
@ -892,17 +870,12 @@ class MachineManager(QObject):
|
||||
"quality_changes": stack_quality_changes
|
||||
})
|
||||
|
||||
has_user_interaction = False
|
||||
# show the keep/discard dialog after the containers have been switched. Otherwise, the default values on
|
||||
# the dialog will be the those before the switching.
|
||||
self._executeDelayedActiveContainerStackChanges()
|
||||
|
||||
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
|
||||
# Show the keep/discard user settings dialog
|
||||
has_user_interaction = Application.getInstance().discardOrKeepProfileChanges()
|
||||
|
||||
# If there is no interaction with the user (it means the dialog showing "keep" or "discard" was not shown)
|
||||
# because either there are not user changes or because the used already decided to always keep or discard,
|
||||
# then the quality instance container is replaced, in which case, the activeQualityChanged signal is emitted.
|
||||
if not has_user_interaction:
|
||||
self._executeDelayedActiveContainerStackChanges()
|
||||
Application.getInstance().discardOrKeepProfileChanges()
|
||||
|
||||
## 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)
|
||||
@ -947,35 +920,46 @@ class MachineManager(QObject):
|
||||
global_container_stack = self._global_container_stack
|
||||
if not global_container_stack:
|
||||
return []
|
||||
|
||||
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom())
|
||||
|
||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
if extruder_stacks:
|
||||
stacks = extruder_stacks
|
||||
else:
|
||||
stacks = [global_container_stack]
|
||||
|
||||
for stack in stacks:
|
||||
material = stack.material
|
||||
# find qualities for extruders
|
||||
for extruder_stack in extruder_stacks:
|
||||
material = extruder_stack.material
|
||||
|
||||
# TODO: fix this
|
||||
if self._new_material_container and stack.getId() == self._active_container_stack.getId():
|
||||
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])
|
||||
|
||||
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:
|
||||
# Add an extra entry for the global stack.
|
||||
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")
|
||||
|
||||
# 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:
|
||||
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
|
||||
|
||||
@ -988,10 +972,8 @@ class MachineManager(QObject):
|
||||
quality_manager = QualityManager.getInstance()
|
||||
|
||||
global_container_stack = self._global_container_stack
|
||||
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom())
|
||||
|
||||
quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name,
|
||||
global_machine_definition)
|
||||
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.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]
|
||||
if global_quality_changes:
|
||||
@ -1002,27 +984,22 @@ class MachineManager(QObject):
|
||||
|
||||
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
|
||||
# the quality changes profile and also satisfies any material constraints.
|
||||
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()
|
||||
|
||||
for stack in extruder_stacks:
|
||||
extruder_definition = quality_manager.getParentMachineDefinition(stack.getBottom())
|
||||
# append the extruder quality changes
|
||||
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:
|
||||
quality_changes = quality_changes_list[0]
|
||||
else:
|
||||
@ -1030,24 +1007,39 @@ class MachineManager(QObject):
|
||||
if not quality_changes:
|
||||
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() == stack.getId():
|
||||
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])
|
||||
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
|
||||
|
||||
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")
|
||||
|
||||
# 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:
|
||||
global_quality = self._empty_quality_container
|
||||
result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes})
|
||||
else:
|
||||
result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": global_quality_changes})
|
||||
|
||||
result.append({
|
||||
"stack": global_container_stack,
|
||||
"quality": global_quality,
|
||||
"quality_changes": global_quality_changes
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@ -1156,10 +1148,11 @@ class MachineManager(QObject):
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def renameMachine(self, machine_id: str, new_name: str):
|
||||
containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||
if containers:
|
||||
new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName())
|
||||
containers[0].setName(new_name)
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
machine_stack = container_registry.findContainerStacks(id = machine_id)
|
||||
if machine_stack:
|
||||
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()
|
||||
|
||||
@pyqtSlot(str)
|
||||
@ -1183,15 +1176,14 @@ class MachineManager(QObject):
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
def hasMaterials(self) -> bool:
|
||||
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
|
||||
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
def hasVariants(self) -> bool:
|
||||
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
|
||||
|
||||
## Property to indicate if a machine has "specialized" material profiles.
|
||||
@ -1199,8 +1191,7 @@ class MachineManager(QObject):
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
def filterMaterialsByMachine(self) -> bool:
|
||||
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
|
||||
|
||||
## Property to indicate if a machine has "specialized" quality profiles.
|
||||
@ -1208,7 +1199,7 @@ class MachineManager(QObject):
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
def filterQualityByMachine(self) -> bool:
|
||||
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
|
||||
|
||||
## Get the Definition ID of a machine (specified by ID)
|
||||
@ -1221,7 +1212,7 @@ class MachineManager(QObject):
|
||||
return containers[0].getBottom().getId()
|
||||
|
||||
@staticmethod
|
||||
def createMachineManager(engine=None, script_engine=None):
|
||||
def createMachineManager():
|
||||
return MachineManager()
|
||||
|
||||
@deprecated("Use ExtruderStack.material = ... and it won't be necessary", "2.7")
|
||||
|
@ -19,3 +19,7 @@ class MaterialsModel(InstanceContainersModel):
|
||||
def _onContainerMetaDataChanged(self, container):
|
||||
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
|
||||
self._update()
|
||||
|
||||
def _onContainerChanged(self, container):
|
||||
if container.getMetaDataEntry("type", "") == "material":
|
||||
super()._onContainerChanged(container)
|
||||
|
@ -12,6 +12,11 @@ from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
|
||||
from cura.QualityManager import QualityManager
|
||||
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.
|
||||
#
|
||||
@ -27,7 +32,6 @@ class ProfilesModel(InstanceContainersModel):
|
||||
self.addRoleName(self.AvailableRole, "available")
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._update)
|
||||
|
||||
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
|
||||
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
|
||||
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
|
||||
@ -54,17 +58,11 @@ class ProfilesModel(InstanceContainersModel):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack is None:
|
||||
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.
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
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
|
||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
||||
materials = [extruder.material for extruder in extruder_stacks]
|
||||
|
||||
# Fetch the list of usable qualities across all extruders.
|
||||
@ -100,32 +98,12 @@ class ProfilesModel(InstanceContainersModel):
|
||||
if global_container_stack is None:
|
||||
return
|
||||
|
||||
# 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()
|
||||
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
|
||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
# Get a list of usable/available qualities for this machine and material
|
||||
qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
|
||||
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
|
||||
unit = global_container_stack.getBottom().getProperty("layer_height", "unit")
|
||||
if not unit:
|
||||
unit = ""
|
||||
@ -190,6 +168,8 @@ class ProfilesModel(InstanceContainersModel):
|
||||
yield item
|
||||
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_type = profile.getMetaDataEntry("quality_type", None)
|
||||
if quality_type:
|
||||
@ -201,7 +181,8 @@ class ProfilesModel(InstanceContainersModel):
|
||||
else:
|
||||
# No global container stack in the 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:
|
||||
quality = None
|
||||
if quality and quality.hasProperty("layer_height", "value"):
|
||||
@ -211,13 +192,27 @@ class ProfilesModel(InstanceContainersModel):
|
||||
|
||||
# Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
|
||||
skip_until_container = global_container_stack.material
|
||||
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): #No material in stack.
|
||||
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack.
|
||||
skip_until_container = global_container_stack.variant
|
||||
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): #No variant in stack.
|
||||
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack.
|
||||
skip_until_container = global_container_stack.getBottom()
|
||||
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
|
||||
|
||||
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_without_unit"] = str(value)
|
||||
|
@ -22,47 +22,23 @@ class QualityAndUserProfilesModel(ProfilesModel):
|
||||
|
||||
# Fetch the list of quality changes.
|
||||
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)
|
||||
|
||||
# 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()
|
||||
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]
|
||||
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
|
||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
||||
|
||||
# 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.
|
||||
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack,
|
||||
extruder_stacks)
|
||||
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
|
||||
|
||||
# Filter the quality_change by the list of available quality_types
|
||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
||||
|
||||
if multiple_extrusion:
|
||||
# If the printer has multiple extruders then quality changes related to the current extruder are kept
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and
|
||||
filtered_quality_changes = [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") == active_extruder.definition.getMetaDataEntry("quality_definition") or
|
||||
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())]
|
||||
else:
|
||||
# If not, the quality changes of the global stack are selected
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and
|
||||
qc.getMetaDataEntry("extruder") is None]
|
||||
|
||||
return quality_list + filtered_quality_changes
|
||||
|
@ -224,7 +224,6 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||
if self._extruder_id == "" and settable_per_extruder:
|
||||
continue
|
||||
|
||||
|
||||
label = definition.label
|
||||
if self._i18n_catalog:
|
||||
label = self._i18n_catalog.i18nc(definition.key + " label", label)
|
||||
|
@ -47,21 +47,20 @@ class SettingInheritanceManager(QObject):
|
||||
|
||||
@pyqtSlot(str, str, result = "QStringList")
|
||||
def getOverridesForExtruder(self, key, extruder_index):
|
||||
multi_extrusion = self._global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
if not multi_extrusion:
|
||||
return self._settings_with_inheritance_warning
|
||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||
if not extruder:
|
||||
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
|
||||
return []
|
||||
result = []
|
||||
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key=key)
|
||||
extruder_stack = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||
if not extruder_stack:
|
||||
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
|
||||
return result
|
||||
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key = key)
|
||||
if not definitions:
|
||||
Logger.log("w", "Could not find definition for key [%s] (2)", key)
|
||||
return []
|
||||
result = []
|
||||
return result
|
||||
|
||||
for key in definitions[0].getAllKeys():
|
||||
if self._settingIsOverwritingInheritance(key, extruder):
|
||||
if self._settingIsOverwritingInheritance(key, extruder_stack):
|
||||
result.append(key)
|
||||
|
||||
return result
|
||||
@ -78,8 +77,8 @@ class SettingInheritanceManager(QObject):
|
||||
|
||||
def _onActiveExtruderChanged(self):
|
||||
new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
if not new_active_stack:
|
||||
new_active_stack = self._global_container_stack
|
||||
# if not new_active_stack:
|
||||
# new_active_stack = self._global_container_stack
|
||||
|
||||
if new_active_stack != self._active_container_stack: # Check if changed
|
||||
if self._active_container_stack: # Disconnect signal from old container (if any)
|
||||
|
@ -27,11 +27,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
self._stack = PerObjectContainerStack(stack_id = id(self))
|
||||
self._stack.setDirty(False) # This stack does not need to be saved.
|
||||
self._stack.addContainer(InstanceContainer(container_id = "SettingOverrideInstanceContainer"))
|
||||
|
||||
if ExtruderManager.getInstance().extruderCount > 1:
|
||||
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
|
||||
else:
|
||||
self._extruder_stack = None
|
||||
|
||||
self._stack.propertyChanged.connect(self._onSettingChanged)
|
||||
|
||||
|
@ -22,47 +22,23 @@ class UserProfilesModel(ProfilesModel):
|
||||
|
||||
# Fetch the list of quality changes.
|
||||
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)
|
||||
|
||||
# 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()
|
||||
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]
|
||||
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
|
||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
||||
|
||||
# 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.
|
||||
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack,
|
||||
extruder_stacks)
|
||||
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
|
||||
|
||||
# Filter the quality_change by the list of available quality_types
|
||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
||||
|
||||
if multiple_extrusion:
|
||||
# If the printer has multiple extruders then quality changes related to the current extruder are kept
|
||||
filtered_quality_changes = [qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and
|
||||
filtered_quality_changes = [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") == active_extruder.definition.getMetaDataEntry("quality_definition") or
|
||||
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())]
|
||||
else:
|
||||
# If not, the quality changes of the global stack are selected
|
||||
filtered_quality_changes = [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
|
||||
|
@ -107,20 +107,13 @@ class ThreeMFReader(MeshReader):
|
||||
um_node.addDecorator(SettingOverrideDecorator())
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
# Ensure the correct next container for the SettingOverride decorator is set.
|
||||
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)
|
||||
|
||||
if default_stack:
|
||||
default_stack_id = default_stack.getId()
|
||||
else:
|
||||
default_stack_id = global_container_stack.getId()
|
||||
um_node.callDecoration("setActiveExtruder", default_stack_id)
|
||||
um_node.callDecoration("setActiveExtruder", default_stack.getId())
|
||||
|
||||
# Get the definition & set it
|
||||
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
|
||||
@ -139,7 +132,7 @@ class ThreeMFReader(MeshReader):
|
||||
else:
|
||||
Logger.log("w", "Unable to find extruder in position %s", setting_value)
|
||||
continue
|
||||
setting_container.setProperty(key,"value", setting_value)
|
||||
setting_container.setProperty(key, "value", setting_value)
|
||||
|
||||
if len(um_node.getChildren()) > 0:
|
||||
group_decorator = GroupDecorator()
|
||||
|
@ -13,6 +13,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase
|
||||
from UM.Job import Job
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Util import parseBool
|
||||
from .WorkspaceDialog import WorkspaceDialog
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
@ -152,7 +153,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
|
||||
if not definitions:
|
||||
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)
|
||||
|
||||
else:
|
||||
definition_container = definitions[0]
|
||||
@ -208,7 +210,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
instance_container = InstanceContainer(container_id)
|
||||
|
||||
# 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)
|
||||
|
||||
container_type = instance_container.getMetaDataEntry("type")
|
||||
@ -378,7 +381,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
return WorkspaceReader.PreReadResult.accepted
|
||||
|
||||
## 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
|
||||
extruder_config = configparser.ConfigParser()
|
||||
extruder_config.read_string(extruder_file_content)
|
||||
@ -394,7 +397,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
return None
|
||||
|
||||
# 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 extruder_stack
|
||||
@ -484,7 +487,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
definitions = self._container_registry.findDefinitionContainers(id = container_id)
|
||||
if not definitions:
|
||||
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)
|
||||
Job.yieldThread()
|
||||
|
||||
@ -502,18 +506,21 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
|
||||
if not materials:
|
||||
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)
|
||||
else:
|
||||
material_container = materials[0]
|
||||
if not material_container.isReadOnly(): # Only create new materials if they are not read only.
|
||||
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":
|
||||
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
||||
# auto created & added.
|
||||
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)
|
||||
|
||||
material_containers.append(material_container)
|
||||
@ -540,7 +547,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
instance_container = InstanceContainer(container_id)
|
||||
|
||||
# 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")
|
||||
Job.yieldThread()
|
||||
|
||||
@ -562,7 +569,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
else:
|
||||
if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
|
||||
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)
|
||||
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.
|
||||
@ -595,7 +603,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# selected strategy.
|
||||
if self._resolve_strategies[container_type] == "override":
|
||||
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)
|
||||
|
||||
elif self._resolve_strategies[container_type] == "new":
|
||||
@ -644,9 +653,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# Get the stack(s) saved in the workspace.
|
||||
Logger.log("d", "Workspace loading is checking stacks containers...")
|
||||
|
||||
# --
|
||||
# load global stack file
|
||||
try:
|
||||
stack = None
|
||||
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
|
||||
stack = container_stacks[0]
|
||||
@ -655,7 +665,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# There is a machine, check if it has authentication data. If so, keep that data.
|
||||
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
|
||||
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
|
||||
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
|
||||
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"),
|
||||
file_name = global_stack_file)
|
||||
if network_authentication_id:
|
||||
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
|
||||
if network_authentication_key:
|
||||
@ -665,7 +676,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
# create a new global stack
|
||||
stack = GlobalStack(global_stack_id_new)
|
||||
# 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
|
||||
stack._id = global_stack_id_new
|
||||
@ -682,12 +694,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
self._container_registry.addContainer(stack)
|
||||
containers_added.append(stack)
|
||||
else:
|
||||
Logger.log("e", "Resolve strategy of %s for machine is not supported",
|
||||
self._resolve_strategies["machine"])
|
||||
Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
|
||||
|
||||
# Create a new definition_changes container if it was empty
|
||||
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
|
||||
Job.yieldThread()
|
||||
except:
|
||||
@ -697,16 +708,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
self._container_registry.removeContainer(container.getId())
|
||||
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
|
||||
try:
|
||||
for extruder_stack_file in extruder_stack_files:
|
||||
@ -714,9 +715,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
|
||||
|
||||
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)
|
||||
stack = self._overrideExtruderStack(global_stack, extruder_file_content)
|
||||
stack = self._overrideExtruderStack(global_stack, extruder_file_content, extruder_stack_file)
|
||||
if stack is None:
|
||||
continue
|
||||
|
||||
@ -736,7 +736,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
extruder_config.write(tmp_string_io)
|
||||
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
|
||||
stack._id = new_id
|
||||
@ -749,9 +749,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
|
||||
# Create a new definition_changes container if it was empty
|
||||
if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
|
||||
stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings"))
|
||||
if global_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
|
||||
|
||||
if stack.getMetaDataEntry("type") == "extruder_train":
|
||||
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:
|
||||
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.
|
||||
@ -759,6 +776,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
self._container_registry.removeContainer(container.getId())
|
||||
return
|
||||
|
||||
|
||||
# Check quality profiles to make sure that if one stack has the "not supported" quality profile,
|
||||
# all others should have the same.
|
||||
#
|
||||
@ -773,18 +791,97 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
#
|
||||
has_not_supported = False
|
||||
for stack in [global_stack] + extruder_stacks:
|
||||
if stack.quality.getId() == "empty_quality":
|
||||
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:
|
||||
available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack, extruder_stacks)
|
||||
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".
|
||||
# When resolve is "new", some containers will get renamed, so all the other containers that reference to those
|
||||
# MUST get updated too.
|
||||
@ -807,7 +904,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
global_stack.userChanges = container
|
||||
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":
|
||||
# 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:
|
||||
@ -907,7 +1008,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
base_file_name = os.path.basename(file_name)
|
||||
if base_file_name.endswith(".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
|
||||
|
||||
## HACK: Replaces the material container in the given stack with a newly created material container.
|
||||
|
@ -1,12 +1,10 @@
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, pyqtProperty, QCoreApplication
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
@ -256,14 +254,8 @@ class WorkspaceDialog(QObject):
|
||||
return self._result
|
||||
|
||||
def _createViewFromQML(self):
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url))
|
||||
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
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())
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url)
|
||||
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
def show(self):
|
||||
# Emit signal so the right thread actually shows the view.
|
||||
|
@ -87,7 +87,7 @@ class ThreeMFWriter(MeshWriter):
|
||||
if stack is not None:
|
||||
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:
|
||||
changed_setting_keys.add("extruder_nr")
|
||||
|
||||
|
@ -8,8 +8,7 @@ from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Version import Version
|
||||
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, QObject
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
|
||||
import os.path
|
||||
import collections
|
||||
@ -106,9 +105,5 @@ class ChangeLog(Extension, QObject,):
|
||||
self._changelog_window.hide()
|
||||
|
||||
def createChangelogWindow(self):
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml"))
|
||||
|
||||
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)
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")
|
||||
self._changelog_window = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
@ -639,3 +639,165 @@ Prints the outer walls with a jittering motion to give your object a diffuse fin
|
||||
|
||||
*Wire Printing
|
||||
The object is printed with a mid-air / net-like structure, following the mesh surface. The build plate will move up and down during diagonal segments. Though not visible in layer view, you can view the result in other software, such as Repetier Host or http://chilipeppr.com/tinyg.
|
||||
|
||||
|
||||
[15.06 Beta]
|
||||
|
||||
Cura 15.06 is a new release built from the ground up on a completely new
|
||||
framework called Uranium. This framework has been designed to make it easier to
|
||||
extend Cura with additional functionality as well as provide a cleaner UI.
|
||||
|
||||
[15.05.95]
|
||||
|
||||
* Fixed: Selection ghost remains visible after deleting an object
|
||||
* Fixed: Window does not show up immediately after starting application on OSX
|
||||
* Fixed: Added display of rotation angle during rotation
|
||||
* Fixed: Object changes position while rotating/scaling
|
||||
* Fixed: Loading improvements in the layer view
|
||||
* Fixed: Added application icons
|
||||
* Fixed: Improved feedback when loading models
|
||||
* Fixed: Eject device on MacOSX now provides proper feedback
|
||||
* Fixed: Make it possible to show retraction settings for UM2
|
||||
* Fixed: Opening the machine preferences page will switch to the first available machine
|
||||
* Fixed: Improved tool handle hit area size
|
||||
* Fixed: Render lines with a thickness based on screen DPI
|
||||
|
||||
[15.05.94]
|
||||
|
||||
* Added Russian translations
|
||||
* Fixed: Infill not displayed in layer view
|
||||
* Fixed: Cannot select/scale/rotate when first activating the tool and then trying to select a model.
|
||||
* Fixed: Improved font rendering on Windows
|
||||
* Fixed: Help > Show Documentation crashes Cura on Windows
|
||||
* Fixed: "There is no disk in the drive" repeating messages on Windows
|
||||
* Fixed: Retraction settings not visible for Ultimaker2
|
||||
* Fixed: Display rotation angle when rotating an object
|
||||
* Fixed: Time/Quality slider values are properly rounded
|
||||
* Fixed: Improved clarity of buttons and text
|
||||
* Fixed: No indication that anything is happening when loading a model
|
||||
* Fixed: Eject device now works on Windows
|
||||
|
||||
[15.05.93]
|
||||
|
||||
* Fixed: No shortcuts for moving up/down layers in layer view.
|
||||
* Fixed: Last view layers could not be scrolled through in layer view.
|
||||
* Fixed: Files provided on command line would not actually show up on the build
|
||||
platform.
|
||||
* Fixed: Render a ghost of the selection in Layer view to make the actual object
|
||||
position clear.
|
||||
* Fixed: Showing a menu would clear the selection.
|
||||
* Fixed: Size and scaling factor display for scale tool.
|
||||
* Fixed: Missing background for additional tool controls.
|
||||
* Fixed: Loading message times out when loading large files.
|
||||
* Fixed: Show recent files in the file menu.
|
||||
* Fixed: Windows installer will now install MSVC 2010 redistributable, to
|
||||
prevent issues with missing DLL's.
|
||||
* Fixed: Collapsed/expanded state of setting categories not stored.
|
||||
|
||||
[15.05.91]
|
||||
|
||||
* There is now a working MacOSX version. Currently it supports OSX 10.7 and
|
||||
higher.
|
||||
* Fixed: Need to deselect before selecting a different object.
|
||||
* Fixed: Object can be moved on Z axis.
|
||||
* Fixed: Error values should be considered invalid values and will not trigger a
|
||||
slice.
|
||||
* Fixed: Text fields used a locale-aware validator while the underlying code did
|
||||
not.
|
||||
* Fixed: Text fields will trigger a slice on text change, not only after focus
|
||||
change/enter press.
|
||||
* Fixed: Rotate Tool snaps to incorrect value.
|
||||
* Fixed: Object Collision would only moved objects to the right.
|
||||
* Fixed: Object Collision would move the selected object when it should not.
|
||||
* Fixed: Camera panning now works correctly instead of doing nothing.
|
||||
* Fixed: Camera would flip around center point at maximum rotation.
|
||||
* Fixed: Build platform grid blocked view from below objects.
|
||||
* Fixed: Viewport on MacOSX with high-DPI screens was only taking 1/4th of the
|
||||
window
|
||||
|
||||
[15.05.90]
|
||||
|
||||
* Fixed: Additional UI elements for tools and views not loading.
|
||||
* Fixed: Double click needed to change setting dialog page.
|
||||
* Fixed: Context menu entries (reload, center object, etc.) not working.
|
||||
* Fixed: "Open With" or passing files from command line not working.
|
||||
* Fixed: "Reload All" would not reload files.
|
||||
|
||||
In addition, a lot of work has gone into getting a usable Mac OSX version.
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
* Plugin based system
|
||||
The Uranium framework provides us with a plugin-based system
|
||||
that provides additional flexibility when extending Cura. Think
|
||||
of new views, tools, file formats, etc. This is probably the
|
||||
biggest new feature.
|
||||
* Improved UI
|
||||
The UI has received a complete overhaul.
|
||||
* Time-Quality Slider
|
||||
The 4 static quick print profiles have been replaced with
|
||||
a slider that should make it easier to find the right spot
|
||||
between print time and print quality.
|
||||
* More Settings
|
||||
The Advanced mode is now configurable and can show many
|
||||
additional settings that were previously not available, while at
|
||||
the same time not overwhelming new users with too many settings.
|
||||
Custom set of visible settings can be created by the user.
|
||||
* Support for high-DPI screens
|
||||
The refreshed UI has been designed with high-DPI screens in
|
||||
mind which should improve the experience of Cura on such
|
||||
devices.
|
||||
* Improved language support
|
||||
(Not yet available for the Beta release.)
|
||||
* Improved support structure generation
|
||||
The new version of the CuraEngine now features improved
|
||||
support generation algorithms and additional options for support
|
||||
structure generation.
|
||||
* Experimental Feature: Wire Printing
|
||||
Wire Printing has been added as an experimental new feature. It
|
||||
will print objects as a structure of lines. It can be enabled by
|
||||
from Advanced Mode -> Fixes -> Wire Printing.
|
||||
* Undo/Redo
|
||||
It is now possible to undo and redo most scene operations, like
|
||||
moving or rotating objects.
|
||||
|
||||
Features from earlier versions not (yet) in this release
|
||||
--------------------------------------------------------
|
||||
|
||||
* The All-at-once/One-at-a-time toggle is not available.
|
||||
We are working on an improved implementation of this mechanism
|
||||
but it will not be available for this release.
|
||||
* No dual extrusion features are available yet.
|
||||
We are working on a completely new workflow for this but this
|
||||
needs additional time.
|
||||
* “Lay Flat” has been removed.
|
||||
The existing implementation was unfortunately not salvageable.
|
||||
We will be looking into an improved implementation for this
|
||||
feature.
|
||||
* "Split Object Into Parts" has been removed.
|
||||
Due to the same reason as Lay Flat.
|
||||
* Support for AMF and DAE file formats has been removed.
|
||||
Both of these will be implemented as plugins in the future.
|
||||
* Support for directly loading a GCode file is not yet available.
|
||||
This will be implemented as a plugin in the future.
|
||||
* Support for PNG, JPG and other image formats has been removed.
|
||||
These can be supported by a plugin with an improved UI.
|
||||
* Support for loading Minecraft levels has been removed.
|
||||
This can be implemented as a plugin.
|
||||
* Windows XP support has been dropped.
|
||||
Microsoft is no longer supporting xp, so they no longer back
|
||||
port certain features that we require.
|
||||
* X-Ray view is missing.
|
||||
Will be implemented as a (you might have guessed it) plugin.
|
||||
* Fixes: Follow Mesh Surface
|
||||
Has been removed from the engine, the same result can be
|
||||
achieved using no infill or top/bottom layers.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
* Some OBJ files are rendered as black objects due to missing
|
||||
normals.
|
||||
* Disabling plugins does not work correctly yet.
|
||||
* Unicorn occasionally still requires feeding. Do not feed it
|
||||
after midnight.
|
||||
|
@ -61,6 +61,8 @@ message Polygon {
|
||||
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)
|
||||
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 {
|
||||
@ -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 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_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
|
||||
}
|
||||
|
||||
|
||||
|
@ -301,6 +301,26 @@ class CuraEngineBackend(QObject, Backend):
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
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 Application.getInstance().platformActivity:
|
||||
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):
|
||||
if 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
|
||||
# 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.
|
||||
|
@ -61,7 +61,7 @@ class ProcessSlicedLayersJob(Job):
|
||||
|
||||
def run(self):
|
||||
start_time = time()
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
|
||||
self._progress_message.show()
|
||||
Job.yieldThread()
|
||||
if self._abort_requested:
|
||||
@ -95,20 +95,27 @@ class ProcessSlicedLayersJob(Job):
|
||||
|
||||
# Find the minimum layer number
|
||||
# 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
|
||||
negative_layers = 0
|
||||
for layer in self._layers:
|
||||
if layer.id < min_layer_number:
|
||||
min_layer_number = layer.id
|
||||
if layer.id < 0:
|
||||
negative_layers += 1
|
||||
|
||||
current_layer = 0
|
||||
|
||||
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)
|
||||
this_layer = layer_data.getLayer(abs_layer_number)
|
||||
layer_data.setLayerHeight(abs_layer_number, layer.height)
|
||||
layer_data.setLayerThickness(abs_layer_number, layer.thickness)
|
||||
|
||||
for p in range(layer.repeatedMessageCount("path_segment")):
|
||||
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 = 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.
|
||||
# Currently the infill layer thickness also translates to line width
|
||||
line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4")
|
||||
line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter
|
||||
line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array
|
||||
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_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.
|
||||
# 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[:, 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_layer.polygons.append(this_poly)
|
||||
@ -219,7 +227,7 @@ class ProcessSlicedLayersJob(Job):
|
||||
self._progress_message.setProgress(100)
|
||||
|
||||
view = Application.getInstance().getController().getActiveView()
|
||||
if view.getPluginId() == "LayerView":
|
||||
if view.getPluginId() == "SimulationView":
|
||||
view.resetLayerData()
|
||||
|
||||
if self._progress_message:
|
||||
@ -232,7 +240,7 @@ class ProcessSlicedLayersJob(Job):
|
||||
|
||||
def _onActiveViewChanged(self):
|
||||
if self.isRunning():
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
|
||||
if not self._progress_message:
|
||||
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information"))
|
||||
if self._progress_message.getProgress() != 100:
|
||||
|
@ -26,6 +26,7 @@ class StartJobResult(IntEnum):
|
||||
NothingToSlice = 4
|
||||
MaterialIncompatible = 5
|
||||
BuildPlateError = 6
|
||||
ObjectSettingError = 7 #When an error occurs in per-object settings.
|
||||
|
||||
|
||||
## Formatter class that handles token expansion in start/end gcod
|
||||
@ -105,7 +106,7 @@ class StartSliceJob(Job):
|
||||
continue
|
||||
|
||||
if self._checkStackForErrors(node.callDecoration("getStack")):
|
||||
self.setResult(StartJobResult.SettingError)
|
||||
self.setResult(StartJobResult.ObjectSettingError)
|
||||
return
|
||||
|
||||
with self._scene.getSceneLock():
|
||||
@ -158,13 +159,9 @@ class StartSliceJob(Job):
|
||||
self._buildGlobalSettingsMessage(stack)
|
||||
self._buildGlobalInheritsStackMessage(stack)
|
||||
|
||||
# Only add extruder stacks if there are multiple extruders
|
||||
# Single extruder machines only use the global stack to store setting values
|
||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
# Build messages for extruder stacks
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
self._buildExtruderMessage(extruder_stack)
|
||||
else:
|
||||
self._buildExtruderMessageFromGlobalStack(stack)
|
||||
|
||||
for group in object_groups:
|
||||
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")
|
||||
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.
|
||||
#
|
||||
# The settings are taken from the global stack. This does not include any
|
||||
|
462
plugins/GCodeReader/FlavorParser.py
Normal file
462
plugins/GCodeReader/FlavorParser.py
Normal file
@ -0,0 +1,462 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Backend import Backend
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Message import Message
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Preferences import Preferences
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
from cura import LayerDataBuilder
|
||||
from cura import LayerDataDecorator
|
||||
from cura.LayerPolygon import LayerPolygon
|
||||
from cura.GCodeListDecorator import GCodeListDecorator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
import numpy
|
||||
import math
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
# This parser is intented for interpret the common firmware codes among all the different flavors
|
||||
class FlavorParser:
|
||||
|
||||
def __init__(self):
|
||||
Application.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
||||
self._cancelled = False
|
||||
self._message = None
|
||||
self._layer_number = 0
|
||||
self._extruder_number = 0
|
||||
self._clearValues()
|
||||
self._scene_node = None
|
||||
# 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._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
|
||||
self._current_layer_thickness = 0.2 # default
|
||||
|
||||
Preferences.getInstance().addPreference("gcodereader/show_caution", True)
|
||||
|
||||
def _clearValues(self):
|
||||
self._filament_diameter = 2.85
|
||||
self._extruder_number = 0
|
||||
self._extrusion_length_offset = [0]
|
||||
self._layer_type = LayerPolygon.Inset0Type
|
||||
self._layer_number = 0
|
||||
self._previous_z = 0
|
||||
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
|
||||
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
|
||||
def _getValue(line, code):
|
||||
n = line.find(code)
|
||||
if n < 0:
|
||||
return None
|
||||
n += len(code)
|
||||
pattern = re.compile("[;\s]")
|
||||
match = pattern.search(line, n)
|
||||
m = match.start() if match is not None else -1
|
||||
try:
|
||||
if m < 0:
|
||||
return line[n:]
|
||||
return line[n:m]
|
||||
except:
|
||||
return None
|
||||
|
||||
def _getInt(self, line, code):
|
||||
value = self._getValue(line, code)
|
||||
try:
|
||||
return int(value)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _getFloat(self, line, code):
|
||||
value = self._getValue(line, code)
|
||||
try:
|
||||
return float(value)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _onHideMessage(self, message):
|
||||
if message == self._message:
|
||||
self._cancelled = True
|
||||
|
||||
@staticmethod
|
||||
def _getNullBoundingBox():
|
||||
return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10))
|
||||
|
||||
def _createPolygon(self, layer_thickness, path, extruder_offsets):
|
||||
countvalid = 0
|
||||
for point in path:
|
||||
if point[5] > 0:
|
||||
countvalid += 1
|
||||
if countvalid >= 2:
|
||||
# we know what to do now, no need to count further
|
||||
continue
|
||||
if countvalid < 2:
|
||||
return False
|
||||
try:
|
||||
self._layer_data_builder.addLayer(self._layer_number)
|
||||
self._layer_data_builder.setLayerHeight(self._layer_number, path[0][2])
|
||||
self._layer_data_builder.setLayerThickness(self._layer_number, layer_thickness)
|
||||
this_layer = self._layer_data_builder.getLayer(self._layer_number)
|
||||
except ValueError:
|
||||
return False
|
||||
count = len(path)
|
||||
line_types = numpy.empty((count - 1, 1), numpy.int32)
|
||||
line_widths = numpy.empty((count - 1, 1), numpy.float32)
|
||||
line_thicknesses = numpy.empty((count - 1, 1), numpy.float32)
|
||||
line_feedrates = numpy.empty((count - 1, 1), numpy.float32)
|
||||
line_widths[:, 0] = 0.35 # Just a guess
|
||||
line_thicknesses[:, 0] = layer_thickness
|
||||
points = numpy.empty((count, 3), numpy.float32)
|
||||
extrusion_values = numpy.empty((count, 1), numpy.float32)
|
||||
i = 0
|
||||
for point in path:
|
||||
points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]]
|
||||
extrusion_values[i] = point[4]
|
||||
if i > 0:
|
||||
line_feedrates[i - 1] = point[3]
|
||||
line_types[i - 1] = point[5]
|
||||
if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
|
||||
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
|
||||
|
||||
this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses, line_feedrates)
|
||||
this_poly.buildCache()
|
||||
|
||||
this_layer.polygons.append(this_poly)
|
||||
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):
|
||||
x, y, z, f, e = position
|
||||
|
||||
if self._is_absolute_positioning:
|
||||
x = params.x if params.x is not None else x
|
||||
y = params.y if params.y is not None else y
|
||||
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:
|
||||
new_extrusion_value = params.e if self._is_absolute_extrusion else e[self._extruder_number] + params.e
|
||||
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:
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
|
||||
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
|
||||
# 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):
|
||||
self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
|
||||
self._previous_z = z
|
||||
else:
|
||||
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, f, e)
|
||||
|
||||
|
||||
# G0 and G1 should be handled exactly the same.
|
||||
_gCode1 = _gCode0
|
||||
|
||||
## Home the head.
|
||||
def _gCode28(self, position, params, path):
|
||||
return self._position(
|
||||
params.x if params.x is not None else position.x,
|
||||
params.y if params.y is not None else position.y,
|
||||
params.z if params.z is not None else position.z,
|
||||
position.f,
|
||||
position.e)
|
||||
|
||||
## Set the absolute positioning
|
||||
def _gCode90(self, position, params, path):
|
||||
self._is_absolute_positioning = True
|
||||
self._is_absolute_extrusion = True
|
||||
return position
|
||||
|
||||
## Set the relative positioning
|
||||
def _gCode91(self, position, params, path):
|
||||
self._is_absolute_positioning = False
|
||||
self._is_absolute_extrusion = False
|
||||
return position
|
||||
|
||||
## Reset the current position to the values specified.
|
||||
# For example: G92 X10 will set the X to 10 without any physical motion.
|
||||
def _gCode92(self, position, params, path):
|
||||
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
|
||||
return self._position(
|
||||
params.x if params.x is not None else position.x,
|
||||
params.y if params.y is not None else position.y,
|
||||
params.z if params.z is not None else position.z,
|
||||
params.f if params.f is not None else position.f,
|
||||
position.e)
|
||||
|
||||
def processGCode(self, G, line, position, path):
|
||||
func = getattr(self, "_gCode%s" % G, None)
|
||||
line = line.split(";", 1)[0] # Remove comments (if any)
|
||||
if func is not None:
|
||||
s = line.upper().split(" ")
|
||||
x, y, z, f, e = None, None, None, None, None
|
||||
for item in s[1:]:
|
||||
if len(item) <= 1:
|
||||
continue
|
||||
if item.startswith(";"):
|
||||
continue
|
||||
if item[0] == "X":
|
||||
x = float(item[1:])
|
||||
if item[0] == "Y":
|
||||
y = float(item[1:])
|
||||
if item[0] == "Z":
|
||||
z = float(item[1:])
|
||||
if item[0] == "F":
|
||||
f = float(item[1:]) / 60
|
||||
if item[0] == "E":
|
||||
e = float(item[1:])
|
||||
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
|
||||
params = self._position(x, y, z, f, e)
|
||||
return func(position, params, path)
|
||||
return position
|
||||
|
||||
def processTCode(self, T, line, position, path):
|
||||
self._extruder_number = T
|
||||
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))
|
||||
return position
|
||||
|
||||
def processMCode(self, M, line, position, path):
|
||||
pass
|
||||
|
||||
_type_keyword = ";TYPE:"
|
||||
_layer_keyword = ";LAYER:"
|
||||
|
||||
## For showing correct x, y offsets for each extruder
|
||||
def _extruderOffsets(self):
|
||||
result = {}
|
||||
for extruder in ExtruderManager.getInstance().getExtruderStacks():
|
||||
result[int(extruder.getMetaData().get("position", "0"))] = [
|
||||
extruder.getProperty("machine_nozzle_offset_x", "value"),
|
||||
extruder.getProperty("machine_nozzle_offset_y", "value")]
|
||||
return result
|
||||
|
||||
def processGCodeFile(self, file_name):
|
||||
Logger.log("d", "Preparing to load %s" % file_name)
|
||||
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()
|
||||
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
|
||||
# real data to calculate it from.
|
||||
scene_node.getBoundingBox = self._getNullBoundingBox
|
||||
|
||||
gcode_list = []
|
||||
self._is_layers_in_file = False
|
||||
|
||||
Logger.log("d", "Opening file %s" % file_name)
|
||||
|
||||
self._extruder_offsets = self._extruderOffsets() # dict with index the extruder number. can be empty
|
||||
|
||||
with open(file_name, "r") as file:
|
||||
file_lines = 0
|
||||
current_line = 0
|
||||
for line in file:
|
||||
file_lines += 1
|
||||
gcode_list.append(line)
|
||||
if not self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
|
||||
self._is_layers_in_file = True
|
||||
file.seek(0)
|
||||
|
||||
file_step = max(math.floor(file_lines / 100), 1)
|
||||
|
||||
self._clearValues()
|
||||
|
||||
self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"),
|
||||
lifetime=0,
|
||||
title = catalog.i18nc("@info:title", "G-code Details"))
|
||||
|
||||
self._message.setProgress(0)
|
||||
self._message.show()
|
||||
|
||||
Logger.log("d", "Parsing %s..." % file_name)
|
||||
|
||||
current_position = self._position(0, 0, 0, 0, [0])
|
||||
current_path = []
|
||||
min_layer_number = 0
|
||||
negative_layers = 0
|
||||
previous_layer = 0
|
||||
|
||||
for line in file:
|
||||
if self._cancelled:
|
||||
Logger.log("d", "Parsing %s cancelled" % file_name)
|
||||
return None
|
||||
current_line += 1
|
||||
|
||||
if current_line % file_step == 0:
|
||||
self._message.setProgress(math.floor(current_line / file_lines * 100))
|
||||
Job.yieldThread()
|
||||
if len(line) == 0:
|
||||
continue
|
||||
|
||||
if line.find(self._type_keyword) == 0:
|
||||
type = line[len(self._type_keyword):].strip()
|
||||
if type == "WALL-INNER":
|
||||
self._layer_type = LayerPolygon.InsetXType
|
||||
elif type == "WALL-OUTER":
|
||||
self._layer_type = LayerPolygon.Inset0Type
|
||||
elif type == "SKIN":
|
||||
self._layer_type = LayerPolygon.SkinType
|
||||
elif type == "SKIRT":
|
||||
self._layer_type = LayerPolygon.SkirtType
|
||||
elif type == "SUPPORT":
|
||||
self._layer_type = LayerPolygon.SupportType
|
||||
elif type == "FILL":
|
||||
self._layer_type = LayerPolygon.InfillType
|
||||
else:
|
||||
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:
|
||||
try:
|
||||
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]))
|
||||
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
|
||||
previous_layer = layer_number
|
||||
except:
|
||||
pass
|
||||
|
||||
# This line is a comment. Ignore it (except for the layer_keyword)
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
|
||||
G = self._getInt(line, "G")
|
||||
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)
|
||||
continue
|
||||
|
||||
# When changing the extruder, the polygon with the stored paths is computed
|
||||
if line.startswith("T"):
|
||||
T = self._getInt(line, "T")
|
||||
if T is not None:
|
||||
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
|
||||
current_path.clear()
|
||||
|
||||
current_position = self.processTCode(T, line, current_position, current_path)
|
||||
|
||||
if line.startswith("M"):
|
||||
M = self._getInt(line, "M")
|
||||
self.processMCode(M, line, current_position, current_path)
|
||||
|
||||
# "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])):
|
||||
self._layer_number += 1
|
||||
current_path.clear()
|
||||
|
||||
material_color_map = numpy.zeros((10, 4), dtype = numpy.float32)
|
||||
material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0]
|
||||
material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0]
|
||||
layer_mesh = self._layer_data_builder.build(material_color_map)
|
||||
decorator = LayerDataDecorator.LayerDataDecorator()
|
||||
decorator.setLayerData(layer_mesh)
|
||||
scene_node.addDecorator(decorator)
|
||||
|
||||
gcode_list_decorator = GCodeListDecorator()
|
||||
gcode_list_decorator.setGCodeList(gcode_list)
|
||||
scene_node.addDecorator(gcode_list_decorator)
|
||||
|
||||
Application.getInstance().getController().getScene().gcode_list = gcode_list
|
||||
|
||||
Logger.log("d", "Finished parsing %s" % file_name)
|
||||
self._message.hide()
|
||||
|
||||
if self._layer_number == 0:
|
||||
Logger.log("w", "File %s doesn't contain any valid layers" % file_name)
|
||||
|
||||
settings = Application.getInstance().getGlobalContainerStack()
|
||||
machine_width = settings.getProperty("machine_width", "value")
|
||||
machine_depth = settings.getProperty("machine_depth", "value")
|
||||
|
||||
if not self._center_is_zero:
|
||||
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
|
||||
|
||||
Logger.log("d", "Loaded %s" % file_name)
|
||||
|
||||
if Preferences.getInstance().getValue("gcodereader/show_caution"):
|
||||
caution_message = Message(catalog.i18nc(
|
||||
"@info:generic",
|
||||
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."),
|
||||
lifetime=0,
|
||||
title = catalog.i18nc("@info:title", "G-code Details"))
|
||||
caution_message.show()
|
||||
|
||||
# The "save/print" button's state is bound to the backend state.
|
||||
backend = Application.getInstance().getBackend()
|
||||
backend.backendStateChange.emit(Backend.BackendState.Disabled)
|
||||
|
||||
return scene_node
|
@ -1,377 +1,44 @@
|
||||
# Copyright (c) 2016 Aleph Objects, Inc.
|
||||
# Copyright (c) 2017 Aleph Objects, Inc.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Backend import Backend
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.FileHandler.FileReader import FileReader
|
||||
from UM.Mesh.MeshReader import MeshReader
|
||||
from UM.Message import Message
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Preferences import Preferences
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
from cura import LayerDataBuilder
|
||||
from cura import LayerDataDecorator
|
||||
from cura.LayerPolygon import LayerPolygon
|
||||
from cura.GCodeListDecorator import GCodeListDecorator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
import numpy
|
||||
import math
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
from . import MarlinFlavorParser, RepRapFlavorParser
|
||||
|
||||
# Class for loading and parsing G-code files
|
||||
class GCodeReader(MeshReader):
|
||||
|
||||
_flavor_default = "Marlin"
|
||||
_flavor_keyword = ";FLAVOR:"
|
||||
_flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(),
|
||||
"Marlin" : MarlinFlavorParser.MarlinFlavorParser()}
|
||||
|
||||
def __init__(self):
|
||||
super(GCodeReader, self).__init__()
|
||||
self._supported_extensions = [".gcode", ".g"]
|
||||
Application.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
||||
self._cancelled = False
|
||||
self._message = None
|
||||
self._layer_number = 0
|
||||
self._extruder_number = 0
|
||||
self._clearValues()
|
||||
self._scene_node = None
|
||||
self._position = namedtuple('Position', ['x', 'y', 'z', 'e'])
|
||||
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._current_layer_thickness = 0.2 # default
|
||||
self._flavor_reader = None
|
||||
|
||||
Preferences.getInstance().addPreference("gcodereader/show_caution", True)
|
||||
|
||||
def _clearValues(self):
|
||||
self._extruder_number = 0
|
||||
self._layer_type = LayerPolygon.Inset0Type
|
||||
self._layer_number = 0
|
||||
self._previous_z = 0
|
||||
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
|
||||
self._center_is_zero = False
|
||||
|
||||
@staticmethod
|
||||
def _getValue(line, code):
|
||||
n = line.find(code)
|
||||
if n < 0:
|
||||
return None
|
||||
n += len(code)
|
||||
pattern = re.compile("[;\s]")
|
||||
match = pattern.search(line, n)
|
||||
m = match.start() if match is not None else -1
|
||||
# PreRead is used to get the correct flavor. If not, Marlin is set by default
|
||||
def preRead(self, file_name, *args, **kwargs):
|
||||
with open(file_name, "r") as file:
|
||||
for line in file:
|
||||
if line[:len(self._flavor_keyword)] == self._flavor_keyword:
|
||||
try:
|
||||
if m < 0:
|
||||
return line[n:]
|
||||
return line[n:m]
|
||||
self._flavor_reader = self._flavor_readers_dict[line[len(self._flavor_keyword):].rstrip()]
|
||||
return FileReader.PreReadResult.accepted
|
||||
except:
|
||||
return None
|
||||
# If there is no entry in the dictionary for this flavor, just skip and select the by-default flavor
|
||||
break
|
||||
|
||||
def _getInt(self, line, code):
|
||||
value = self._getValue(line, code)
|
||||
try:
|
||||
return int(value)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _getFloat(self, line, code):
|
||||
value = self._getValue(line, code)
|
||||
try:
|
||||
return float(value)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _onHideMessage(self, message):
|
||||
if message == self._message:
|
||||
self._cancelled = True
|
||||
|
||||
@staticmethod
|
||||
def _getNullBoundingBox():
|
||||
return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10))
|
||||
|
||||
def _createPolygon(self, layer_thickness, path, extruder_offsets):
|
||||
countvalid = 0
|
||||
for point in path:
|
||||
if point[3] > 0:
|
||||
countvalid += 1
|
||||
if countvalid >= 2:
|
||||
# we know what to do now, no need to count further
|
||||
continue
|
||||
if countvalid < 2:
|
||||
return False
|
||||
try:
|
||||
self._layer_data_builder.addLayer(self._layer_number)
|
||||
self._layer_data_builder.setLayerHeight(self._layer_number, path[0][2])
|
||||
self._layer_data_builder.setLayerThickness(self._layer_number, layer_thickness)
|
||||
this_layer = self._layer_data_builder.getLayer(self._layer_number)
|
||||
except ValueError:
|
||||
return False
|
||||
count = len(path)
|
||||
line_types = numpy.empty((count - 1, 1), numpy.int32)
|
||||
line_widths = 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_widths[:, 0] = 0.35 # Just a guess
|
||||
line_thicknesses[:, 0] = layer_thickness
|
||||
points = numpy.empty((count, 3), numpy.float32)
|
||||
i = 0
|
||||
for point in path:
|
||||
points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]]
|
||||
if i > 0:
|
||||
line_types[i - 1] = point[3]
|
||||
if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
|
||||
line_widths[i - 1] = 0.1
|
||||
i += 1
|
||||
|
||||
this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses)
|
||||
this_poly.buildCache()
|
||||
|
||||
this_layer.polygons.append(this_poly)
|
||||
return True
|
||||
|
||||
def _gCode0(self, position, params, path):
|
||||
x, y, z, e = position
|
||||
x = params.x if params.x is not None else x
|
||||
y = params.y if params.y is not None else y
|
||||
z = params.z if params.z is not None else position.z
|
||||
|
||||
if params.e is not None:
|
||||
if params.e > e[self._extruder_number]:
|
||||
path.append([x, y, z, self._layer_type]) # extrusion
|
||||
else:
|
||||
path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction
|
||||
e[self._extruder_number] = params.e
|
||||
|
||||
# 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.
|
||||
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._previous_z = z
|
||||
else:
|
||||
path.append([x, y, z, LayerPolygon.MoveCombingType])
|
||||
return self._position(x, y, z, e)
|
||||
|
||||
# G0 and G1 should be handled exactly the same.
|
||||
_gCode1 = _gCode0
|
||||
|
||||
## Home the head.
|
||||
def _gCode28(self, position, params, path):
|
||||
return self._position(
|
||||
params.x if params.x is not None else position.x,
|
||||
params.y if params.y is not None else position.y,
|
||||
0,
|
||||
position.e)
|
||||
|
||||
## Reset the current position to the values specified.
|
||||
# For example: G92 X10 will set the X to 10 without any physical motion.
|
||||
def _gCode92(self, position, params, path):
|
||||
if params.e is not None:
|
||||
position.e[self._extruder_number] = params.e
|
||||
return self._position(
|
||||
params.x if params.x is not None else position.x,
|
||||
params.y if params.y is not None else position.y,
|
||||
params.z if params.z is not None else position.z,
|
||||
position.e)
|
||||
|
||||
def _processGCode(self, G, line, position, path):
|
||||
func = getattr(self, "_gCode%s" % G, None)
|
||||
line = line.split(";", 1)[0] # Remove comments (if any)
|
||||
if func is not None:
|
||||
s = line.upper().split(" ")
|
||||
x, y, z, e = None, None, None, None
|
||||
for item in s[1:]:
|
||||
if len(item) <= 1:
|
||||
continue
|
||||
if item.startswith(";"):
|
||||
continue
|
||||
if item[0] == "X":
|
||||
x = float(item[1:])
|
||||
if item[0] == "Y":
|
||||
y = float(item[1:])
|
||||
if item[0] == "Z":
|
||||
z = float(item[1:])
|
||||
if item[0] == "E":
|
||||
e = float(item[1:])
|
||||
if (x is not None and x < 0) or (y is not None and y < 0):
|
||||
self._center_is_zero = True
|
||||
params = self._position(x, y, z, e)
|
||||
return func(position, params, path)
|
||||
return position
|
||||
|
||||
def _processTCode(self, T, line, position, path):
|
||||
self._extruder_number = T
|
||||
if self._extruder_number + 1 > len(position.e):
|
||||
position.e.extend([0] * (self._extruder_number - len(position.e) + 1))
|
||||
return position
|
||||
|
||||
_type_keyword = ";TYPE:"
|
||||
_layer_keyword = ";LAYER:"
|
||||
|
||||
## For showing correct x, y offsets for each extruder
|
||||
def _extruderOffsets(self):
|
||||
result = {}
|
||||
for extruder in ExtruderManager.getInstance().getExtruderStacks():
|
||||
result[int(extruder.getMetaData().get("position", "0"))] = [
|
||||
extruder.getProperty("machine_nozzle_offset_x", "value"),
|
||||
extruder.getProperty("machine_nozzle_offset_y", "value")]
|
||||
return result
|
||||
# If no flavor is found in the GCode, then we use the by-default
|
||||
self._flavor_reader = self._flavor_readers_dict[self._flavor_default]
|
||||
return FileReader.PreReadResult.accepted
|
||||
|
||||
def read(self, file_name):
|
||||
Logger.log("d", "Preparing to load %s" % file_name)
|
||||
self._cancelled = False
|
||||
|
||||
scene_node = SceneNode()
|
||||
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
|
||||
# real data to calculate it from.
|
||||
scene_node.getBoundingBox = self._getNullBoundingBox
|
||||
|
||||
gcode_list = []
|
||||
self._is_layers_in_file = False
|
||||
|
||||
Logger.log("d", "Opening file %s" % file_name)
|
||||
|
||||
self._extruder_offsets = self._extruderOffsets() # dict with index the extruder number. can be empty
|
||||
|
||||
last_z = 0
|
||||
with open(file_name, "r") as file:
|
||||
file_lines = 0
|
||||
current_line = 0
|
||||
for line in file:
|
||||
file_lines += 1
|
||||
gcode_list.append(line)
|
||||
if not self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
|
||||
self._is_layers_in_file = True
|
||||
file.seek(0)
|
||||
|
||||
file_step = max(math.floor(file_lines / 100), 1)
|
||||
|
||||
self._clearValues()
|
||||
|
||||
self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"),
|
||||
lifetime=0,
|
||||
title = catalog.i18nc("@info:title", "G-code Details"))
|
||||
|
||||
self._message.setProgress(0)
|
||||
self._message.show()
|
||||
|
||||
Logger.log("d", "Parsing %s..." % file_name)
|
||||
|
||||
current_position = self._position(0, 0, 0, [0])
|
||||
current_path = []
|
||||
|
||||
for line in file:
|
||||
if self._cancelled:
|
||||
Logger.log("d", "Parsing %s cancelled" % file_name)
|
||||
return None
|
||||
current_line += 1
|
||||
last_z = current_position.z
|
||||
|
||||
if current_line % file_step == 0:
|
||||
self._message.setProgress(math.floor(current_line / file_lines * 100))
|
||||
Job.yieldThread()
|
||||
if len(line) == 0:
|
||||
continue
|
||||
|
||||
if line.find(self._type_keyword) == 0:
|
||||
type = line[len(self._type_keyword):].strip()
|
||||
if type == "WALL-INNER":
|
||||
self._layer_type = LayerPolygon.InsetXType
|
||||
elif type == "WALL-OUTER":
|
||||
self._layer_type = LayerPolygon.Inset0Type
|
||||
elif type == "SKIN":
|
||||
self._layer_type = LayerPolygon.SkinType
|
||||
elif type == "SKIRT":
|
||||
self._layer_type = LayerPolygon.SkirtType
|
||||
elif type == "SUPPORT":
|
||||
self._layer_type = LayerPolygon.SupportType
|
||||
elif type == "FILL":
|
||||
self._layer_type = LayerPolygon.InfillType
|
||||
else:
|
||||
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
|
||||
|
||||
if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
|
||||
try:
|
||||
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]))
|
||||
current_path.clear()
|
||||
self._layer_number = layer_number
|
||||
except:
|
||||
pass
|
||||
|
||||
# This line is a comment. Ignore it (except for the layer_keyword)
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
|
||||
G = self._getInt(line, "G")
|
||||
if G is not None:
|
||||
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
|
||||
|
||||
if line.startswith("T"):
|
||||
T = self._getInt(line, "T")
|
||||
if T is not None:
|
||||
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
|
||||
current_path.clear()
|
||||
|
||||
current_position = self._processTCode(T, line, current_position, current_path)
|
||||
|
||||
# "Flush" leftovers
|
||||
if not self._is_layers_in_file and len(current_path) > 1:
|
||||
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
|
||||
self._layer_number += 1
|
||||
current_path.clear()
|
||||
|
||||
material_color_map = numpy.zeros((10, 4), dtype = numpy.float32)
|
||||
material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0]
|
||||
material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0]
|
||||
layer_mesh = self._layer_data_builder.build(material_color_map)
|
||||
decorator = LayerDataDecorator.LayerDataDecorator()
|
||||
decorator.setLayerData(layer_mesh)
|
||||
scene_node.addDecorator(decorator)
|
||||
|
||||
gcode_list_decorator = GCodeListDecorator()
|
||||
gcode_list_decorator.setGCodeList(gcode_list)
|
||||
scene_node.addDecorator(gcode_list_decorator)
|
||||
|
||||
Application.getInstance().getController().getScene().gcode_list = gcode_list
|
||||
|
||||
Logger.log("d", "Finished parsing %s" % file_name)
|
||||
self._message.hide()
|
||||
|
||||
if self._layer_number == 0:
|
||||
Logger.log("w", "File %s doesn't contain any valid layers" % file_name)
|
||||
|
||||
settings = Application.getInstance().getGlobalContainerStack()
|
||||
machine_width = settings.getProperty("machine_width", "value")
|
||||
machine_depth = settings.getProperty("machine_depth", "value")
|
||||
|
||||
if not self._center_is_zero:
|
||||
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
|
||||
|
||||
Logger.log("d", "Loaded %s" % file_name)
|
||||
|
||||
if Preferences.getInstance().getValue("gcodereader/show_caution"):
|
||||
caution_message = Message(catalog.i18nc(
|
||||
"@info:generic",
|
||||
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."),
|
||||
lifetime=0,
|
||||
title = catalog.i18nc("@info:title", "G-code Details"))
|
||||
caution_message.show()
|
||||
|
||||
# The "save/print" button's state is bound to the backend state.
|
||||
backend = Application.getInstance().getBackend()
|
||||
backend.backendStateChange.emit(Backend.BackendState.Disabled)
|
||||
|
||||
return scene_node
|
||||
return self._flavor_reader.processGCodeFile(file_name)
|
||||
|
10
plugins/GCodeReader/MarlinFlavorParser.py
Normal file
10
plugins/GCodeReader/MarlinFlavorParser.py
Normal file
@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import FlavorParser
|
||||
|
||||
# This parser is intented for interpret the Marlin/Sprinter Firmware flavor
|
||||
class MarlinFlavorParser(FlavorParser.FlavorParser):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
32
plugins/GCodeReader/RepRapFlavorParser.py
Normal file
32
plugins/GCodeReader/RepRapFlavorParser.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import FlavorParser
|
||||
|
||||
# This parser is intented for interpret the RepRap Firmware flavor
|
||||
class RepRapFlavorParser(FlavorParser.FlavorParser):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def processMCode(self, M, line, position, path):
|
||||
if M == 82:
|
||||
# Set absolute extrusion mode
|
||||
self._is_absolute_extrusion = True
|
||||
elif M == 83:
|
||||
# Set relative extrusion mode
|
||||
self._is_absolute_extrusion = False
|
||||
|
||||
## Set the absolute positioning
|
||||
# RepRapFlavor code G90 sets position of X, Y, Z, and E to absolute
|
||||
def _gCode90(self, position, params, path):
|
||||
self._is_absolute_positioning = True
|
||||
self._is_absolute_extrusion = True
|
||||
return position
|
||||
|
||||
## Set the relative positioning
|
||||
# RepRapFlavor code G91 sets position of X, Y, Z to relative
|
||||
# For relative E, M83 is used
|
||||
def _gCode91(self, position, params, path):
|
||||
self._is_absolute_positioning = False
|
||||
return position
|
@ -4,8 +4,7 @@
|
||||
import os
|
||||
import threading
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QObject
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QObject
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
@ -81,14 +80,9 @@ class ImageReaderUI(QObject):
|
||||
def _createConfigUI(self):
|
||||
if self._ui_view is None:
|
||||
Logger.log("d", "Creating ImageReader config UI")
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("ImageReader"), "ConfigUI.qml"))
|
||||
component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
self._ui_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._ui_context.setContextProperty("manager", self)
|
||||
self._ui_view = component.create(self._ui_context)
|
||||
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("ImageReader"), "ConfigUI.qml")
|
||||
self._ui_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
self._ui_view.setFlags(self._ui_view.flags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMinimizeButtonHint & ~Qt.WindowMaximizeButtonHint);
|
||||
|
||||
self._disable_size_callbacks = False
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -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()
|
@ -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")
|
||||
}
|
||||
}
|
@ -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)
|
@ -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() }
|
@ -116,8 +116,7 @@ class MachineSettingsAction(MachineAction):
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setMachineExtruderCount(self, extruder_count):
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
extruder_manager = Application.getInstance().getExtruderManager()
|
||||
|
||||
definition_changes_container = self._global_container_stack.definitionChanges
|
||||
if not self._global_container_stack or definition_changes_container == self._empty_container:
|
||||
@ -127,34 +126,6 @@ class MachineSettingsAction(MachineAction):
|
||||
if extruder_count == previous_extruder_count:
|
||||
return
|
||||
|
||||
extruder_material_id = None
|
||||
extruder_variant_id = None
|
||||
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
|
||||
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
|
||||
|
||||
if settable_per_extruder:
|
||||
limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder")
|
||||
|
||||
if limit_to_extruder == "-1" or limit_to_extruder == extruder_index:
|
||||
global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value"))
|
||||
extruder_user_container.removeInstance(setting_key)
|
||||
|
||||
# reset all extruder number settings whose value is no longer valid
|
||||
for setting_instance in self._global_container_stack.userChanges.findInstances():
|
||||
setting_key = setting_instance.definition.key
|
||||
@ -177,14 +148,13 @@ class MachineSettingsAction(MachineAction):
|
||||
|
||||
definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
|
||||
|
||||
if extruder_count > 1:
|
||||
# Multiextrusion
|
||||
|
||||
# Make sure one of the extruder stacks is active
|
||||
if extruder_manager.activeExtruderIndex == -1:
|
||||
extruder_manager.setActiveExtruderIndex(0)
|
||||
|
||||
# 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:
|
||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
global_user_container = self._global_container_stack.getTop()
|
||||
@ -192,37 +162,15 @@ class MachineSettingsAction(MachineAction):
|
||||
for setting_instance in global_user_container.findInstances():
|
||||
setting_key = setting_instance.definition.key
|
||||
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
|
||||
|
||||
if settable_per_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.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
|
||||
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()
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def forceUpdate(self):
|
||||
# Force rebuilding the build volume by reloading the global container stack.
|
||||
@ -275,16 +223,13 @@ class MachineSettingsAction(MachineAction):
|
||||
if not self._global_container_stack.getMetaDataEntry("has_materials", False):
|
||||
return
|
||||
|
||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
if machine_extruder_count > 1:
|
||||
material = ExtruderManager.getInstance().getActiveExtruderStack().material
|
||||
else:
|
||||
material = self._global_container_stack.material
|
||||
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_approximate_diameter = str(round(material_diameter))
|
||||
|
||||
material_approximate_diameter = str(round(material_diameter))
|
||||
definition_changes = self._global_container_stack.definitionChanges
|
||||
machine_diameter = definition_changes.getProperty("material_diameter", "value")
|
||||
if not machine_diameter:
|
||||
@ -294,10 +239,7 @@ class MachineSettingsAction(MachineAction):
|
||||
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.")
|
||||
|
||||
if machine_extruder_count > 1:
|
||||
stacks = ExtruderManager.getInstance().getExtruderStacks()
|
||||
else:
|
||||
stacks = [self._global_container_stack]
|
||||
|
||||
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
|
||||
materials_definition = self._global_container_stack.definition.getId()
|
||||
@ -338,7 +280,7 @@ class MachineSettingsAction(MachineAction):
|
||||
search_criteria["id"] = stack.getMetaDataEntry("preferred_material")
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
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)
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
|
@ -355,7 +355,7 @@ Cura.MachineAction
|
||||
if(currentIndex > 0)
|
||||
{
|
||||
contentItem.forceActiveFocus();
|
||||
ExtruderManager.setActiveExtruderIndex(currentIndex - 1);
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(currentIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,11 +585,11 @@ Cura.MachineAction
|
||||
propertyProvider.setPropertyValue("value", text);
|
||||
if(_forceUpdateOnChange)
|
||||
{
|
||||
var extruderIndex = ExtruderManager.activeExtruderIndex;
|
||||
var extruderIndex = Cura.ExtruderManager.activeExtruderIndex;
|
||||
manager.forceUpdate();
|
||||
if(ExtruderManager.activeExtruderIndex != extruderIndex)
|
||||
if(Cura.ExtruderManager.activeExtruderIndex != extruderIndex)
|
||||
{
|
||||
ExtruderManager.setActiveExtruderIndex(extruderIndex)
|
||||
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex)
|
||||
}
|
||||
}
|
||||
if(_afterOnEditingFinished)
|
||||
|
@ -78,21 +78,16 @@ class PerObjectSettingsTool(Tool):
|
||||
def _onGlobalContainerChanged(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
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
|
||||
|
||||
# Ensure that all extruder data is reset
|
||||
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()
|
||||
extruder_stack = ExtruderManager.getInstance().getExtruderStack(0)
|
||||
|
||||
if extruder_stack:
|
||||
root_node = Application.getInstance().getController().getScene().getRoot()
|
||||
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
|
||||
old_extruder_pos = node.callDecoration("getActiveExtruderPosition")
|
||||
if old_extruder_pos is not None:
|
||||
|
@ -11,7 +11,6 @@ from UM.Message import Message
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
from PyQt5.QtCore import QUrl, QObject, Qt, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
|
||||
import json
|
||||
import os
|
||||
@ -39,8 +38,6 @@ class PluginBrowser(QObject, Extension):
|
||||
self._plugins_metadata = []
|
||||
self._plugins_model = None
|
||||
|
||||
self._qml_component = None
|
||||
self._qml_context = None
|
||||
self._dialog = None
|
||||
self._download_progress = 0
|
||||
|
||||
@ -111,17 +108,8 @@ class PluginBrowser(QObject, Extension):
|
||||
|
||||
def _createDialog(self, qml_name):
|
||||
Logger.log("d", "Creating dialog [%s]", qml_name)
|
||||
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name))
|
||||
self._qml_component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
# We need access to engine (although technically we can't)
|
||||
self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._qml_context.setContextProperty("manager", self)
|
||||
dialog = self._qml_component.create(self._qml_context)
|
||||
if dialog is None:
|
||||
Logger.log("e", "QQmlComponent status %s", self._qml_component.status())
|
||||
Logger.log("e", "QQmlComponent errorString %s", self._qml_component.errorString())
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name)
|
||||
dialog = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
return dialog
|
||||
|
||||
def setIsDownloading(self, is_downloading):
|
||||
|
@ -19,6 +19,7 @@ Item {
|
||||
property color upperHandleColor: "black"
|
||||
property color lowerHandleColor: "black"
|
||||
property color rangeHandleColor: "black"
|
||||
property color handleActiveColor: "white"
|
||||
property real handleLabelWidth: width
|
||||
property var activeHandle: upperHandle
|
||||
|
||||
@ -100,8 +101,8 @@ Item {
|
||||
var lowerValue = sliderRoot.getLowerValueFromSliderHandle()
|
||||
|
||||
// set both values after moving the handle position
|
||||
UM.LayerView.setCurrentLayer(upperValue)
|
||||
UM.LayerView.setMinimumLayer(lowerValue)
|
||||
UM.SimulationView.setCurrentLayer(upperValue)
|
||||
UM.SimulationView.setMinimumLayer(lowerValue)
|
||||
}
|
||||
|
||||
function setValue (value) {
|
||||
@ -109,8 +110,8 @@ Item {
|
||||
value = Math.min(value, sliderRoot.maximumValue)
|
||||
value = Math.max(value, sliderRoot.minimumValue + range)
|
||||
|
||||
UM.LayerView.setCurrentLayer(value)
|
||||
UM.LayerView.setMinimumLayer(value - range)
|
||||
UM.SimulationView.setCurrentLayer(value)
|
||||
UM.SimulationView.setMinimumLayer(value - range)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@ -134,7 +135,7 @@ Item {
|
||||
onPressed: sliderRoot.setActiveHandle(rangeHandle)
|
||||
}
|
||||
|
||||
LayerSliderLabel {
|
||||
SimulationSliderLabel {
|
||||
id: rangleHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
@ -146,7 +147,7 @@ Item {
|
||||
// custom properties
|
||||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.upperValue
|
||||
busy: UM.LayerView.busy
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: rangeHandle.setValue // connect callback functions
|
||||
}
|
||||
}
|
||||
@ -160,7 +161,7 @@ Item {
|
||||
height: sliderRoot.handleSize
|
||||
anchors.horizontalCenter: sliderRoot.horizontalCenter
|
||||
radius: sliderRoot.handleRadius
|
||||
color: sliderRoot.upperHandleColor
|
||||
color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
|
||||
visible: sliderRoot.layersVisible
|
||||
|
||||
function onHandleDragged () {
|
||||
@ -174,7 +175,7 @@ Item {
|
||||
sliderRoot.updateRangeHandle()
|
||||
|
||||
// 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
|
||||
@ -188,7 +189,7 @@ Item {
|
||||
// set the slider position based on the upper value
|
||||
function setValue (value) {
|
||||
|
||||
UM.LayerView.setCurrentLayer(value)
|
||||
UM.SimulationView.setCurrentLayer(value)
|
||||
|
||||
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
|
||||
var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
|
||||
@ -198,6 +199,9 @@ Item {
|
||||
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
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@ -210,10 +214,13 @@ Item {
|
||||
}
|
||||
|
||||
onPositionChanged: parent.onHandleDragged()
|
||||
onPressed: sliderRoot.setActiveHandle(upperHandle)
|
||||
onPressed: {
|
||||
sliderRoot.setActiveHandle(upperHandle)
|
||||
upperHandleLabel.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
LayerSliderLabel {
|
||||
SimulationSliderLabel {
|
||||
id: upperHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
@ -225,7 +232,7 @@ Item {
|
||||
// custom properties
|
||||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.upperValue
|
||||
busy: UM.LayerView.busy
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: upperHandle.setValue // connect callback functions
|
||||
}
|
||||
}
|
||||
@ -239,9 +246,9 @@ Item {
|
||||
height: parent.handleSize
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
radius: sliderRoot.handleRadius
|
||||
color: sliderRoot.lowerHandleColor
|
||||
color: lowerHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.lowerHandleColor
|
||||
|
||||
visible: slider.layersVisible
|
||||
visible: sliderRoot.layersVisible
|
||||
|
||||
function onHandleDragged () {
|
||||
|
||||
@ -254,7 +261,7 @@ Item {
|
||||
sliderRoot.updateRangeHandle()
|
||||
|
||||
// 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
|
||||
@ -268,7 +275,7 @@ Item {
|
||||
// set the slider position based on the lower value
|
||||
function setValue (value) {
|
||||
|
||||
UM.LayerView.setMinimumLayer(value)
|
||||
UM.SimulationView.setMinimumLayer(value)
|
||||
|
||||
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)))
|
||||
@ -278,6 +285,9 @@ Item {
|
||||
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
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@ -290,10 +300,13 @@ Item {
|
||||
}
|
||||
|
||||
onPositionChanged: parent.onHandleDragged()
|
||||
onPressed: sliderRoot.setActiveHandle(lowerHandle)
|
||||
onPressed: {
|
||||
sliderRoot.setActiveHandle(lowerHandle)
|
||||
lowerHandleLabel.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
LayerSliderLabel {
|
||||
SimulationSliderLabel {
|
||||
id: lowerHandleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
@ -305,7 +318,7 @@ Item {
|
||||
// custom properties
|
||||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.lowerValue
|
||||
busy: UM.LayerView.busy
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: lowerHandle.setValue // connect callback functions
|
||||
}
|
||||
}
|
49
plugins/SimulationView/NozzleNode.py
Normal file
49
plugins/SimulationView/NozzleNode.py
Normal 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
|
161
plugins/SimulationView/PathSlider.qml
Normal file
161
plugins/SimulationView/PathSlider.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
190
plugins/SimulationView/SimulationPass.py
Normal file
190
plugins/SimulationView/SimulationPass.py
Normal 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()
|
@ -17,6 +17,7 @@ UM.PointingRectangle {
|
||||
property real value: 0
|
||||
property var setValue // Function
|
||||
property bool busy: false
|
||||
property int startFrom: 1
|
||||
|
||||
target: Qt.point(parent.width, y + height / 2)
|
||||
arrowSize: UM.Theme.getSize("default_arrow").width
|
||||
@ -53,7 +54,7 @@ UM.PointingRectangle {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// key bindings, work when label is currenctly focused (active handle in LayerSlider)
|
||||
@ -74,14 +75,14 @@ UM.PointingRectangle {
|
||||
cursorPosition = 0
|
||||
|
||||
if (valueLabel.text != "") {
|
||||
// -1 because we need to convert back to an array structure
|
||||
sliderLabelRoot.setValue(parseInt(valueLabel.text) - 1)
|
||||
// -startFrom because we need to convert back to an array structure
|
||||
sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom)
|
||||
}
|
||||
}
|
||||
|
||||
validator: IntValidator {
|
||||
bottom: 1
|
||||
top: sliderLabelRoot.maximumValue + 1 // +1 because actual layers is an array
|
||||
bottom:startFrom
|
||||
top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0
|
||||
}
|
||||
}
|
||||
|
217
plugins/LayerView/LayerView.py → plugins/SimulationView/SimulationView.py
Executable file → Normal file
217
plugins/LayerView/LayerView.py → plugins/SimulationView/SimulationView.py
Executable file → Normal 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.
|
||||
|
||||
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.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
|
||||
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 os.path
|
||||
|
||||
## View used to display g-code paths.
|
||||
class LayerView(View):
|
||||
# Must match LayerView.qml
|
||||
class SimulationView(View):
|
||||
# Must match SimulationView.qml
|
||||
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
|
||||
LAYER_VIEW_TYPE_LINE_TYPE = 1
|
||||
LAYER_VIEW_TYPE_FEEDRATE = 2
|
||||
LAYER_VIEW_TYPE_THICKNESS = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@ -54,22 +54,29 @@ class LayerView(View):
|
||||
self._activity = False
|
||||
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._simulation_running = False
|
||||
|
||||
self._ghost_shader = None
|
||||
self._layer_pass = None
|
||||
self._composite_pass = None
|
||||
self._old_layer_bindings = None
|
||||
self._layerview_composite_shader = None
|
||||
self._simulationview_composite_shader = None
|
||||
self._old_composite_shader = None
|
||||
|
||||
self._global_container_stack = None
|
||||
self._proxy = LayerViewProxy.LayerViewProxy()
|
||||
self._proxy = SimulationViewProxy()
|
||||
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
||||
|
||||
self._resetSettings()
|
||||
self._legend_items = None
|
||||
self._show_travel_moves = False
|
||||
self._nozzle_node = None
|
||||
|
||||
Preferences.getInstance().addPreference("view/top_layer_count", 5)
|
||||
Preferences.getInstance().addPreference("view/only_show_top_layers", False)
|
||||
@ -91,7 +98,7 @@ class LayerView(View):
|
||||
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"),
|
||||
title = catalog.i18nc("@info:title", "Layer View"))
|
||||
title = catalog.i18nc("@info:title", "Simulation View"))
|
||||
|
||||
def _resetSettings(self):
|
||||
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
|
||||
@ -101,17 +108,24 @@ class LayerView(View):
|
||||
self._show_helpers = 1
|
||||
self._show_skin = 1
|
||||
self._show_infill = 1
|
||||
self.resetLayerData()
|
||||
|
||||
def getActivity(self):
|
||||
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:
|
||||
# Currently the RenderPass constructor requires a size > 0
|
||||
# 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._layer_pass.setLayerView(self)
|
||||
self._layer_pass.setSimulationView(self)
|
||||
return self._layer_pass
|
||||
|
||||
def getCurrentLayer(self):
|
||||
@ -120,13 +134,27 @@ class LayerView(View):
|
||||
def getMinimumLayer(self):
|
||||
return self._minimum_layer_num
|
||||
|
||||
def _onSceneChanged(self, node):
|
||||
self.calculateMaxLayers()
|
||||
|
||||
def getMaxLayers(self):
|
||||
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()
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
|
||||
def isBusy(self):
|
||||
return self._busy
|
||||
@ -136,9 +164,19 @@ class LayerView(View):
|
||||
self._busy = busy
|
||||
self.busyChanged.emit()
|
||||
|
||||
def isSimulationRunning(self):
|
||||
return self._simulation_running
|
||||
|
||||
def setSimulationRunning(self, running):
|
||||
self._simulation_running = running
|
||||
|
||||
def resetLayerData(self):
|
||||
self._current_layer_mesh = 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):
|
||||
scene = self.getController().getScene()
|
||||
@ -186,15 +224,43 @@ class LayerView(View):
|
||||
|
||||
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
|
||||
#
|
||||
# \param layer_view_type integer as in LayerView.qml and this class
|
||||
def setLayerViewType(self, layer_view_type):
|
||||
# \param layer_view_type integer as in SimulationView.qml and this class
|
||||
def setSimulationViewType(self, layer_view_type):
|
||||
self._layer_view_type = layer_view_type
|
||||
self.currentLayerNumChanged.emit()
|
||||
|
||||
## Return the layer view type, integer as in LayerView.qml and this class
|
||||
def getLayerViewType(self):
|
||||
## Return the layer view type, integer as in SimulationView.qml and this class
|
||||
def getSimulationViewType(self):
|
||||
return self._layer_view_type
|
||||
|
||||
## Set the extruder opacity
|
||||
@ -243,9 +309,20 @@ class LayerView(View):
|
||||
def getExtruderCount(self):
|
||||
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):
|
||||
scene = self.getController().getScene()
|
||||
self._activity = True
|
||||
|
||||
self._old_max_layers = self._max_layers
|
||||
## Recalculate num max layers
|
||||
@ -255,9 +332,16 @@ class LayerView(View):
|
||||
if not layer_data:
|
||||
continue
|
||||
|
||||
self.setActivity(True)
|
||||
min_layer_number = sys.maxsize
|
||||
max_layer_number = -sys.maxsize
|
||||
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:
|
||||
max_layer_number = layer_id
|
||||
if min_layer_number > layer_id:
|
||||
@ -281,10 +365,32 @@ class LayerView(View):
|
||||
self.maxLayersChanged.emit()
|
||||
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()
|
||||
maxPathsChanged = Signal()
|
||||
currentLayerNumChanged = Signal()
|
||||
currentPathNumChanged = Signal()
|
||||
globalStackChanged = 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
|
||||
# as this caused some issues.
|
||||
@ -308,26 +414,31 @@ class LayerView(View):
|
||||
return True
|
||||
|
||||
if event.type == Event.ViewActivateEvent:
|
||||
# Make sure the LayerPass is created
|
||||
layer_pass = self.getLayerPass()
|
||||
# Make sure the SimulationPass is created
|
||||
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)
|
||||
self._onGlobalStackChanged()
|
||||
|
||||
if not self._layerview_composite_shader:
|
||||
self._layerview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layerview_composite.shader"))
|
||||
if not self._simulationview_composite_shader:
|
||||
self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader"))
|
||||
theme = Application.getInstance().getTheme()
|
||||
self._layerview_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_background_color", Color(*theme.getColor("viewport_background").getRgb()))
|
||||
self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
|
||||
|
||||
if not self._composite_pass:
|
||||
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._composite_pass.getLayerBindings().append("layerview")
|
||||
self._composite_pass.getLayerBindings().append("simulationview")
|
||||
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:
|
||||
self._wireprint_warning_message.hide()
|
||||
@ -335,6 +446,7 @@ class LayerView(View):
|
||||
if self._global_container_stack:
|
||||
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.setCompositeShader(self._old_composite_shader)
|
||||
@ -364,6 +476,9 @@ class LayerView(View):
|
||||
else:
|
||||
self._wireprint_warning_message.hide()
|
||||
|
||||
def _onCurrentLayerNumChanged(self):
|
||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||
|
||||
def _startUpdateTopLayers(self):
|
||||
if not self._compatibility_mode:
|
||||
return
|
||||
@ -397,7 +512,7 @@ class LayerView(View):
|
||||
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(
|
||||
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("|")):
|
||||
try:
|
707
plugins/SimulationView/SimulationView.qml
Normal file
707
plugins/SimulationView/SimulationView.qml
Normal 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")
|
||||
}
|
||||
}
|
259
plugins/SimulationView/SimulationViewProxy.py
Normal file
259
plugins/SimulationView/SimulationViewProxy.py
Normal 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)
|
26
plugins/SimulationView/__init__.py
Normal file
26
plugins/SimulationView/__init__.py
Normal 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()}
|
0
plugins/LayerView/layers.shader → plugins/SimulationView/layers.shader
Executable file → Normal file
0
plugins/LayerView/layers.shader → plugins/SimulationView/layers.shader
Executable file → Normal file
42
plugins/LayerView/layers3d.shader → plugins/SimulationView/layers3d.shader
Executable file → Normal file
42
plugins/LayerView/layers3d.shader → plugins/SimulationView/layers3d.shader
Executable file → Normal file
@ -6,6 +6,10 @@ vertex41core =
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewProjectionMatrix;
|
||||
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 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 float a_extruder;
|
||||
in highp float a_line_type;
|
||||
in highp float a_feedrate;
|
||||
in highp float a_thickness;
|
||||
|
||||
out lowp vec4 v_color;
|
||||
|
||||
@ -32,6 +38,28 @@ vertex41core =
|
||||
out highp vec3 f_vertex;
|
||||
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()
|
||||
{
|
||||
vec4 v1_vertex = a_vertex;
|
||||
@ -48,6 +76,12 @@ vertex41core =
|
||||
case 1: // "Line type"
|
||||
v_color = a_color;
|
||||
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;
|
||||
@ -247,6 +281,12 @@ u_show_helpers = 1
|
||||
u_show_skin = 1
|
||||
u_show_infill = 1
|
||||
|
||||
u_min_feedrate = 0
|
||||
u_max_feedrate = 1
|
||||
|
||||
u_min_thickness = 0
|
||||
u_max_thickness = 1
|
||||
|
||||
[bindings]
|
||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
||||
u_modelMatrix = model_matrix
|
||||
@ -262,3 +302,5 @@ a_line_dim = line_dim
|
||||
a_extruder = extruder
|
||||
a_material_color = material_color
|
||||
a_line_type = line_type
|
||||
a_feedrate = feedrate
|
||||
a_thickness = thickness
|
256
plugins/SimulationView/layers3d_shadow.shader
Normal file
256
plugins/SimulationView/layers3d_shadow.shader
Normal 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
|
156
plugins/SimulationView/layers_shadow.shader
Normal file
156
plugins/SimulationView/layers_shadow.shader
Normal 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
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "Layer View",
|
||||
"name": "Simulation View",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the Layer view.",
|
||||
"description": "Provides the Simulation view.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
BIN
plugins/SimulationView/resources/nozzle.stl
Normal file
BIN
plugins/SimulationView/resources/nozzle.stl
Normal file
Binary file not shown.
79
plugins/SimulationView/resources/simulation_pause.svg
Normal file
79
plugins/SimulationView/resources/simulation_pause.svg
Normal 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 |
78
plugins/SimulationView/resources/simulation_resume.svg
Normal file
78
plugins/SimulationView/resources/simulation_resume.svg
Normal 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 |
@ -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","")}
|
||||
|
||||
# add extruder specific data to slice info
|
||||
data["extruders"] = []
|
||||
extruder_count = len(global_container_stack.extruders)
|
||||
extruders = []
|
||||
if extruder_count > 1:
|
||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
|
||||
extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position"))
|
||||
|
||||
if not extruders:
|
||||
extruders = [global_container_stack]
|
||||
|
||||
for extruder in extruders:
|
||||
extruder_dict = dict()
|
||||
extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder
|
||||
|
@ -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_width", 50.0)
|
||||
|
||||
multi_extrusion = False
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
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_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr)
|
||||
if not support_angle_stack:
|
||||
support_angle_stack = global_container_stack
|
||||
else:
|
||||
support_angle_stack = global_container_stack
|
||||
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)
|
||||
|
||||
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")
|
||||
# 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)
|
||||
@ -71,20 +62,12 @@ class SolidView(View):
|
||||
else:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
|
||||
|
||||
|
||||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
if not node.render(renderer):
|
||||
if node.getMeshData() and node.isVisible():
|
||||
uniforms = {}
|
||||
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
|
||||
extruder_index = 0
|
||||
extruder_id = node.callDecoration("getActiveExtruder")
|
||||
@ -98,6 +81,7 @@ class SolidView(View):
|
||||
if extruder_index != ExtruderManager.getInstance().activeExtruderIndex:
|
||||
# Shade objects that are printed with the non-active extruder 25% darker
|
||||
shade_factor = 0.6
|
||||
|
||||
try:
|
||||
# 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])
|
||||
|
@ -1,17 +1,15 @@
|
||||
from cura.MachineAction import MachineAction
|
||||
import os.path
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Logger import Logger
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QUrl, QObject
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
|
||||
import os.path
|
||||
|
||||
import time
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.MachineAction import MachineAction
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
class DiscoverUM3Action(MachineAction):
|
||||
@ -136,17 +134,14 @@ class DiscoverUM3Action(MachineAction):
|
||||
|
||||
def _createAdditionalComponentsView(self):
|
||||
Logger.log("d", "Creating additional ui components for UM3.")
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml"))
|
||||
self.__additional_component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
# We need access to engine (although technically we can't)
|
||||
self.__additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self.__additional_components_context.setContextProperty("manager", self)
|
||||
|
||||
self.__additional_components_view = self.__additional_component.create(self.__additional_components_context)
|
||||
# Create networking dialog
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")
|
||||
self.__additional_components_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
if not self.__additional_components_view:
|
||||
Logger.log("w", "Could not create ui components for UM3.")
|
||||
return
|
||||
|
||||
# Create extra components
|
||||
Application.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
||||
Application.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
|
||||
|
@ -230,13 +230,13 @@ Cura.MachineAction
|
||||
{
|
||||
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")
|
||||
{
|
||||
return catalog.i18nc("@label", "Ultimaker 3 Extended")
|
||||
return catalog.i18nc("@label Printer name", "Ultimaker 3 Extended")
|
||||
} 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
|
||||
|
@ -8,12 +8,10 @@ import time
|
||||
|
||||
from enum import Enum
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QHttpPart, QHttpMultiPart
|
||||
from PyQt5.QtCore import QUrl, QByteArray, pyqtSlot, pyqtProperty, QCoreApplication, QTimer, pyqtSignal, QObject
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, pyqtProperty, QCoreApplication, QTimer, pyqtSignal, QObject
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from UM.Application import Application
|
||||
from UM.Decorators import override
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.OutputDevice import OutputDeviceError
|
||||
@ -131,7 +129,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
@pyqtProperty(QObject, notify=selectedPrinterChanged)
|
||||
def controlItem(self):
|
||||
# TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
|
||||
if not self._control_component:
|
||||
if not self._control_item:
|
||||
self._createControlViewFromQML()
|
||||
name = self._selected_printer.get("friendly_name")
|
||||
if name == self._automatic_printer.get("friendly_name") or name == "":
|
||||
@ -235,17 +233,8 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
|
||||
def spawnPrintView(self):
|
||||
if self._print_view is None:
|
||||
path = QUrl.fromLocalFile(os.path.join(self._plugin_path, "PrintWindow.qml"))
|
||||
component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
self._print_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._print_context.setContextProperty("OutputDevice", self)
|
||||
self._print_view = component.create(self._print_context)
|
||||
|
||||
if component.isError():
|
||||
Logger.log("e", " Errors creating component: \n%s", "\n".join(
|
||||
[e.toString() for e in component.errors()]))
|
||||
|
||||
path = os.path.join(self._plugin_path, "PrintWindow.qml")
|
||||
self._print_view = Application.getInstance().createQmlComponent(path, {"OutputDevice", self})
|
||||
if self._print_view is not None:
|
||||
self._print_view.show()
|
||||
|
||||
@ -486,7 +475,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
||||
|
||||
printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"])
|
||||
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",
|
||||
"Printer '{printer_name}' has finished printing '{job_name}'.")
|
||||
|
@ -44,7 +44,7 @@ Rectangle
|
||||
case "maintenance": // TODO: new string
|
||||
case "unknown":
|
||||
default:
|
||||
return catalog.i18nc("@label", "Unknown");
|
||||
return catalog.i18nc("@label Printer status", "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,13 +16,11 @@ from cura.CuraApplication import CuraApplication
|
||||
|
||||
import threading
|
||||
import platform
|
||||
import glob
|
||||
import time
|
||||
import os.path
|
||||
import serial.tools.list_ports
|
||||
from UM.Extension import Extension
|
||||
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtSlot, pyqtProperty, pyqtSignal, Qt
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
@ -91,12 +89,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
||||
# This will create the view if its not already created.
|
||||
def spawnFirmwareInterface(self, serial_port):
|
||||
if self._firmware_view is None:
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml"))
|
||||
component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
self._firmware_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._firmware_context.setContextProperty("manager", self)
|
||||
self._firmware_view = component.create(self._firmware_context)
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
|
||||
self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
self._firmware_view.show()
|
||||
|
||||
@ -104,6 +98,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
||||
def updateAllFirmware(self, file_name):
|
||||
if file_name.startswith("file://"):
|
||||
file_name = QUrl(file_name).toLocalFile() # File dialogs prepend the path with file://, which we don't need / want
|
||||
|
||||
if not self._usb_output_devices:
|
||||
Message(i18n_catalog.i18nc("@info", "Unable to update firmware because there are no printers connected."), title = i18n_catalog.i18nc("@info:title", "Warning")).show()
|
||||
return
|
||||
|
@ -37,8 +37,7 @@ class UM2UpgradeSelection(MachineAction):
|
||||
def setHasVariants(self, has_variants = True):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
variant_container = global_container_stack.variant
|
||||
variant_index = global_container_stack.getContainerIndex(variant_container)
|
||||
variant_container = global_container_stack.extruders["0"].variant
|
||||
|
||||
if has_variants:
|
||||
if "has_variants" in global_container_stack.getMetaData():
|
||||
@ -52,7 +51,7 @@ class UM2UpgradeSelection(MachineAction):
|
||||
search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" }
|
||||
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
global_container_stack.variant = containers[0]
|
||||
global_container_stack.extruders["0"].variant = containers[0]
|
||||
else:
|
||||
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
||||
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
||||
@ -60,6 +59,6 @@ class UM2UpgradeSelection(MachineAction):
|
||||
global_container_stack.removeMetaDataEntry("has_variants")
|
||||
|
||||
# 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()
|
||||
|
@ -9,13 +9,12 @@ from UM.Logger import Logger
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtSlot
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
|
||||
import os.path
|
||||
|
||||
class UserAgreement(QObject, Extension):
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self):
|
||||
super(UserAgreement, self).__init__()
|
||||
self._user_agreement_window = None
|
||||
self._user_agreement_context = None
|
||||
@ -33,8 +32,8 @@ class UserAgreement(QObject, Extension):
|
||||
self._user_agreement_window.show()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def didAgree(self, userChoice):
|
||||
if userChoice:
|
||||
def didAgree(self, user_choice):
|
||||
if user_choice:
|
||||
Logger.log("i", "User agreed to the user agreement")
|
||||
Preferences.getInstance().setValue("general/accepted_user_agreement", True)
|
||||
self._user_agreement_window.hide()
|
||||
@ -45,9 +44,5 @@ class UserAgreement(QObject, Extension):
|
||||
CuraApplication.getInstance().setNeedToShowUserAgreement(False)
|
||||
|
||||
def createUserAgreementWindow(self):
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "UserAgreement.qml"))
|
||||
|
||||
component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
self._user_agreement_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._user_agreement_context.setContextProperty("manager", self)
|
||||
self._user_agreement_window = component.create(self._user_agreement_context)
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "UserAgreement.qml")
|
||||
self._user_agreement_window = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
@ -3,9 +3,14 @@
|
||||
|
||||
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 = [
|
||||
@ -45,6 +50,15 @@ _OLD_NOT_SUPPORTED_PROFILES = [
|
||||
]
|
||||
|
||||
|
||||
# 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.
|
||||
#
|
||||
@ -97,6 +111,17 @@ class VersionUpgrade30to31(VersionUpgrade):
|
||||
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"
|
||||
@ -126,6 +151,11 @@ class VersionUpgrade30to31(VersionUpgrade):
|
||||
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"] = {}
|
||||
@ -139,3 +169,59 @@ class VersionUpgrade30to31(VersionUpgrade):
|
||||
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())
|
||||
|
@ -422,11 +422,11 @@ class XmlMaterialProfile(InstanceContainer):
|
||||
return version * 1000000 + setting_version
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
def deserialize(self, serialized):
|
||||
def deserialize(self, serialized, file_name = None):
|
||||
containers_to_add = []
|
||||
# update the serialized data first
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
serialized = ContainerInterface.deserialize(self, serialized)
|
||||
serialized = ContainerInterface.deserialize(self, serialized, file_name)
|
||||
|
||||
try:
|
||||
data = ET.fromstring(serialized)
|
||||
@ -549,7 +549,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||
|
||||
definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id)
|
||||
if not definitions:
|
||||
Logger.log("w", "No definition found for machine ID %s", machine_id)
|
||||
# Logger.log("w", "No definition found for machine ID %s", machine_id)
|
||||
continue
|
||||
|
||||
definition = definitions[0]
|
||||
|
@ -7,10 +7,6 @@
|
||||
"visible": true,
|
||||
"author": "rikky",
|
||||
"manufacturer": "101Hero",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "fdmextruder"
|
||||
},
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "101hero-platform.stl",
|
||||
"supports_usb_connection": true
|
||||
|
@ -10,11 +10,7 @@
|
||||
"file_formats": "text/x-gcode",
|
||||
"icon": "icon_ultimaker2",
|
||||
"supports_usb_connection": true,
|
||||
"platform": "3dator_platform.stl",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "fdmextruder"
|
||||
}
|
||||
"platform": "3dator_platform.stl"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
@ -29,7 +25,6 @@
|
||||
"layer_height": { "default_value": 0.2 },
|
||||
"speed_print": { "default_value": 50 },
|
||||
"speed_infill": { "default_value": 60 },
|
||||
"machine_extruder_count": { "default_value": 1 },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
"machine_center_is_zero": { "default_value": false },
|
||||
"machine_height": { "default_value": 260 },
|
||||
|
@ -47,21 +47,24 @@
|
||||
"material_bed_temp_wait": { "default_value": false },
|
||||
"prime_tower_enable": { "default_value": true },
|
||||
"prime_tower_wall_thickness": { "resolve": 0.7 },
|
||||
"prime_tower_position_x": { "value": "50" },
|
||||
"prime_tower_position_y": { "value": "150" },
|
||||
"prime_tower_size": { "value": 24.0 },
|
||||
"prime_tower_position_x": { "value": 125 },
|
||||
"prime_tower_position_y": { "value": 70 },
|
||||
"prime_blob_enable": { "default_value": false },
|
||||
"machine_max_feedrate_z": { "default_value": 20 },
|
||||
"machine_disallowed_areas": { "default_value": [
|
||||
[[215, 135], [-215, 135], [-215, 75], [215, 75]]
|
||||
]},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "\nM92 E159 ;2288 for V5 extruder\n\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nG21\nG90\nM42 S255 P13 ;chamber lights\nM42 S255 P12 ;fume extraction\nM204 S300 ;default acceleration\nM205 X10 ;default jerk\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n"
|
||||
"default_value": "\nM92 E159 ;2288 for V5 extruder\n\nM140 S{material_bed_temperature_layer_0}\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nG21\nG90\nM42 S255 P13 ;chamber lights\nM42 S255 P12 ;fume extraction\nM204 S300 ;default acceleration\nM205 X10 ;default jerk\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "; -- END GCODE --\nM117 cooling down....\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\nM117 Finished.\n; -- end of GCODE --"
|
||||
},
|
||||
"layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
|
||||
"layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
|
||||
"retraction_extra_prime_amount": { "minimum_value_warning": "-2.0" },
|
||||
"optimize_wall_printing_order": { "default_value": true },
|
||||
"machine_nozzle_heat_up_speed": {"default_value": 20},
|
||||
"machine_nozzle_cool_down_speed": {"default_value": 20},
|
||||
"machine_min_cool_heat_time_window": {"default_value": 5}
|
||||
|
@ -20,6 +20,14 @@
|
||||
"machine_depth": {
|
||||
"default_value": 300
|
||||
},
|
||||
"machine_head_polygon": {
|
||||
"default_value": [
|
||||
[-30, 34],
|
||||
[-30, -32],
|
||||
[30, -32],
|
||||
[30, 34]
|
||||
]
|
||||
},
|
||||
"material_diameter": {
|
||||
"default_value": 1.75
|
||||
},
|
||||
|
@ -8,7 +8,7 @@
|
||||
"author": "Dagoma",
|
||||
"manufacturer": "Dagoma",
|
||||
"file_formats": "text/x-gcode",
|
||||
"icon": "icon_ultimaker2.png",
|
||||
"icon": "icon_discoeasy200.png",
|
||||
"platform": "discoeasy200.stl",
|
||||
"platform_offset": [ 105, -59, 280]
|
||||
},
|
||||
@ -30,24 +30,23 @@
|
||||
},
|
||||
"machine_head_with_fans_polygon": {
|
||||
"default_value": [
|
||||
[16, 37],
|
||||
[16, -65],
|
||||
[-16, -65],
|
||||
[16, 37]
|
||||
[17, 70],
|
||||
[17, -40],
|
||||
[-17, -40],
|
||||
[17, 70]
|
||||
]
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 55
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap"
|
||||
"default_value": 10
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": ";Gcode by Cura\nG90 ;absolute positioning\nM106 S250 ;fan on for the palpeur\nG28 X Y\nG1 X50\nM109 S180\nG28\nM104 S{print_temperature}\n;Activation palpeur\n;bloc palpeur\nG29 ;Auto level\nM107 ;start with the fan off\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{print_temperature}\nM140 S{material_bed_temperature}\nM82 ;set extruder to absolute mode\nG92 E0 ;zero the extruded length\nG1 F200 E10 ;extrude 10mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 Z3\nG1 F3000\n"
|
||||
"default_value": ";Gcode by Cura\nG90 ;absolute positioning\nM106 S250 ;fan on for the palpeur\nG28 X Y\nG1 X50\nM109 S180\nG28\nM104 S{material_print_temperature_layer_0}\n;Activation palpeur\n;bloc palpeur\nG29 ;Auto level\nM107 ;start with the fan off\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{material_print_temperature_layer_0}\nM82 ;set extruder to absolute mode\nG92 E0 ;zero the extruded length\nG1 F200 E10 ;extrude 10mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 Z3\nG1 F6000"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "\nM104 S0\nM106 S255 ;start fan full power\nM140 S0 ;heated bed heater off (if you have it)\n;Home machine\nG91 ;relative positioning\nG1 E-1 F{retraction_speed} ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+3 F3000 ;move Z up a bit and retract filament even more\nG90\nG28 X Y\n;Ventilation forcee\nM107 ;stop fan\n;Shut down motor\nM84 ;shut down motors\n"
|
||||
"default_value": "M104 S0\nM106 S255 ;start fan full power\nM140 S0 ;heated bed heater off (if you have it)\n;Home machine\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+3 F3000 ;move Z up a bit and retract filament even more\nG90\nG28 X Y\n;Ventilation forcee\nM107 ;stop fan\n;Shut down motor\nM84 ;shut down motors"
|
||||
},
|
||||
"material_diameter": {
|
||||
"default_value": 1.75
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,8 @@
|
||||
"author": "Ultimaker",
|
||||
"manufacturer": "Unknown",
|
||||
"setting_version": 1,
|
||||
"visible": false
|
||||
"visible": false,
|
||||
"position": "0"
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
|
@ -318,6 +318,17 @@
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"machine_firmware_retract":
|
||||
{
|
||||
"label": "Firmware Retraction",
|
||||
"description": "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"value": "machine_gcode_flavor == 'RepRap (Volumetric)' or machine_gcode_flavor == 'UltiGCode' or machine_gcode_flavor == 'BFB'",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"machine_disallowed_areas":
|
||||
{
|
||||
"label": "Disallowed areas",
|
||||
@ -1208,7 +1219,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fill_perimeter_gaps": {
|
||||
"fill_perimeter_gaps":
|
||||
{
|
||||
"label": "Fill Gaps Between Walls",
|
||||
"description": "Fills the gaps between walls where no walls fit.",
|
||||
"type": "enum",
|
||||
@ -1220,6 +1232,15 @@
|
||||
"limit_to_extruder": "wall_0_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"filter_out_tiny_gaps":
|
||||
{
|
||||
"label": "Filter Out Tiny Gaps",
|
||||
"description": "Filter out tiny gaps to reduce blobs on outside of model.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"limit_to_extruder": "wall_0_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"fill_outline_gaps": {
|
||||
"label": "Print Thin Walls",
|
||||
"description": "Print pieces of the model which are horizontally thinner than the nozzle size.",
|
||||
@ -1340,6 +1361,124 @@
|
||||
"type": "int",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_enabled":
|
||||
{
|
||||
"label": "Enable Ironing",
|
||||
"description": "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_only_highest_layer":
|
||||
{
|
||||
"label": "Iron Only Highest Layer",
|
||||
"description": "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_pattern":
|
||||
{
|
||||
"label": "Ironing Pattern",
|
||||
"description": "The pattern to use for ironing top surfaces.",
|
||||
"type": "enum",
|
||||
"options":
|
||||
{
|
||||
"concentric": "Concentric",
|
||||
"zigzag": "Zig Zag"
|
||||
},
|
||||
"default_value": "zigzag",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_line_spacing":
|
||||
{
|
||||
"label": "Ironing Line Spacing",
|
||||
"description": "The distance between the lines of ironing.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"default_value": 0.1,
|
||||
"minimum_value": "0.001",
|
||||
"maximum_value_warning": "machine_nozzle_tip_outer_diameter",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_flow":
|
||||
{
|
||||
"label": "Ironing Flow",
|
||||
"description": "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface.",
|
||||
"type": "float",
|
||||
"unit": "%",
|
||||
"default_value": 10.0,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "50",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_inset":
|
||||
{
|
||||
"label": "Ironing Inset",
|
||||
"description": "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"default_value": 0.35,
|
||||
"value": "wall_line_width_0 / 2",
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "wall_line_width_0",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"speed_ironing":
|
||||
{
|
||||
"label": "Ironing Speed",
|
||||
"description": "The speed at which to pass over the top surface.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"default_value": 20.0,
|
||||
"value": "speed_topbottom * 20 / 30",
|
||||
"minimum_value": "0.001",
|
||||
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
|
||||
"maximum_value_warning": "100",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"acceleration_ironing":
|
||||
{
|
||||
"label": "Ironing Acceleration",
|
||||
"description": "The acceleration with which ironing is performed.",
|
||||
"unit": "mm/s²",
|
||||
"type": "float",
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"default_value": 3000,
|
||||
"value": "acceleration_topbottom",
|
||||
"enabled": "resolveOrValue('acceleration_enabled') and ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"jerk_ironing":
|
||||
{
|
||||
"label": "Ironing Jerk",
|
||||
"description": "The maximum instantaneous velocity change while performing ironing.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "50",
|
||||
"default_value": 20,
|
||||
"value": "jerk_topbottom",
|
||||
"enabled": "resolveOrValue('jerk_enabled') and ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1440,7 +1579,7 @@
|
||||
"infill_offset_x":
|
||||
{
|
||||
"label": "Infill X Offset",
|
||||
"description": "The infill pattern is offset this distance along the X axis.",
|
||||
"description": "The infill pattern is moved this distance along the X axis.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
@ -1451,7 +1590,7 @@
|
||||
"infill_offset_y":
|
||||
{
|
||||
"label": "Infill Y Offset",
|
||||
"description": "The infill pattern is offset this distance along the Y axis.",
|
||||
"description": "The infill pattern is moved this distance along the Y axis.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
@ -1838,7 +1977,7 @@
|
||||
"material_bed_temperature":
|
||||
{
|
||||
"label": "Build Plate Temperature",
|
||||
"description": "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print.",
|
||||
"description": "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted.",
|
||||
"unit": "°C",
|
||||
"type": "float",
|
||||
"resolve": "max(extruderValues('material_bed_temperature'))",
|
||||
@ -3495,7 +3634,7 @@
|
||||
"minimum_value_warning": "support_line_width",
|
||||
"default_value": 2.66,
|
||||
"enabled": "support_enable",
|
||||
"value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))",
|
||||
"value": "0 if support_infill_rate == 0 else (support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
@ -4827,6 +4966,18 @@
|
||||
"default_value": false,
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"meshfix_maximum_resolution":
|
||||
{
|
||||
"label": "Maximum Resolution",
|
||||
"description": "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"default_value": 0.01,
|
||||
"minimum_value": "0.001",
|
||||
"minimum_value_warning": "0.005",
|
||||
"maximum_value_warning": "0.1",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"multiple_mesh_overlap":
|
||||
{
|
||||
"label": "Merged Meshes Overlap",
|
||||
@ -5075,7 +5226,8 @@
|
||||
"default_value": false,
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_skip_some_zags": {
|
||||
"support_skip_some_zags":
|
||||
{
|
||||
"label": "Break Up Support In Chunks",
|
||||
"description": "Skip some support line connections to make the support structure easier to break away. This setting is applicable to the Zig Zag support infill pattern.",
|
||||
"type": "bool",
|
||||
@ -5083,9 +5235,10 @@
|
||||
"enabled": "support_enable and (support_pattern == 'zigzag')",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children": {
|
||||
"support_skip_zag_per_mm": {
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_skip_zag_per_mm":
|
||||
{
|
||||
"label": "Support Chunk Size",
|
||||
"description": "Leave out a connection between support lines once every N millimeter to make the support structure easier to break away.",
|
||||
"type": "float",
|
||||
@ -5097,13 +5250,15 @@
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children": {
|
||||
"support_zag_skip_count": {
|
||||
"children":
|
||||
{
|
||||
"support_zag_skip_count":
|
||||
{
|
||||
"label": "Support Chunk Line Count",
|
||||
"description": "Skip one in every N connection lines to make the support structure easier to break away.",
|
||||
"type": "int",
|
||||
"default_value": 5,
|
||||
"value": "round(support_skip_zag_per_mm / support_line_distance)",
|
||||
"value": "0 if support_line_distance == 0 else round(support_skip_zag_per_mm / support_line_distance)",
|
||||
"minimum_value": "1",
|
||||
"minimum_value_warning": "3",
|
||||
"enabled": "support_enable and (support_pattern == 'zigzag') and support_skip_some_zags",
|
||||
@ -5112,8 +5267,6 @@
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"draft_shield_enabled":
|
||||
{
|
||||
@ -5457,6 +5610,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"flow_rate_max_extrusion_offset":
|
||||
{
|
||||
"label": "Flow rate compensation max extrusion offset",
|
||||
"description": "The maximum distance in mm to compensate.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"default_value": 0,
|
||||
"value": "0",
|
||||
"enabled": true,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"flow_rate_extrusion_offset_factor":
|
||||
{
|
||||
"label": "Flow rate compensation factor",
|
||||
"description": "The multiplication factor for the flow rate -> distance translation.",
|
||||
"unit": "%",
|
||||
"type": "float",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"default_value": 100,
|
||||
"value": "100",
|
||||
"enabled": true,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"wireframe_enabled":
|
||||
{
|
||||
"label": "Wire Printing",
|
||||
@ -5812,123 +5995,48 @@
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"ironing_enabled":
|
||||
"adaptive_layer_height_enabled":
|
||||
{
|
||||
"label": "Enable Ironing",
|
||||
"description": "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface.",
|
||||
"label": "Use adaptive layers",
|
||||
"description": "Adaptive layers computes the layer heights depending on the shape of the model.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"ironing_only_highest_layer":
|
||||
"adaptive_layer_height_variation":
|
||||
{
|
||||
"label": "Iron Only Highest Layer",
|
||||
"description": "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_pattern":
|
||||
{
|
||||
"label": "Ironing Pattern",
|
||||
"description": "The pattern to use for ironing top surfaces.",
|
||||
"type": "enum",
|
||||
"options":
|
||||
{
|
||||
"concentric": "Concentric",
|
||||
"zigzag": "Zig Zag"
|
||||
},
|
||||
"default_value": "zigzag",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"ironing_line_spacing":
|
||||
{
|
||||
"label": "Ironing Line Spacing",
|
||||
"description": "The distance between the lines of ironing.",
|
||||
"label": "Adaptive layers maximum variation",
|
||||
"description": "The maximum allowed height different from the base layer height in mm.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"enabled": "adaptive_layer_height_enabled",
|
||||
"default_value": 0.1,
|
||||
"minimum_value": "0.001",
|
||||
"maximum_value_warning": "machine_nozzle_tip_outer_diameter",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"ironing_flow":
|
||||
"adaptive_layer_height_variation_step":
|
||||
{
|
||||
"label": "Ironing Flow",
|
||||
"description": "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface.",
|
||||
"label": "Adaptive layers variation step size",
|
||||
"description": "The difference in height of the next layer height compared to the previous one.",
|
||||
"type": "float",
|
||||
"unit": "%",
|
||||
"default_value": 10.0,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "50",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
"enabled": "adaptive_layer_height_enabled",
|
||||
"default_value": 0.01,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"ironing_inset":
|
||||
"adaptive_layer_height_threshold":
|
||||
{
|
||||
"label": "Ironing Inset",
|
||||
"description": "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print.",
|
||||
"label": "Adaptive layers threshold",
|
||||
"description": "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"default_value": 0.35,
|
||||
"value": "wall_line_width_0 / 2",
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "wall_line_width_0",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"speed_ironing":
|
||||
{
|
||||
"label": "Ironing Speed",
|
||||
"description": "The speed at which to pass over the top surface.",
|
||||
"type": "float",
|
||||
"unit": "mm/s",
|
||||
"default_value": 20.0,
|
||||
"value": "speed_topbottom * 20 / 30",
|
||||
"minimum_value": "0.001",
|
||||
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
|
||||
"maximum_value_warning": "100",
|
||||
"enabled": "ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"acceleration_ironing":
|
||||
{
|
||||
"label": "Ironing Acceleration",
|
||||
"description": "The acceleration with which ironing is performed.",
|
||||
"unit": "mm/s²",
|
||||
"type": "float",
|
||||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"default_value": 3000,
|
||||
"value": "acceleration_topbottom",
|
||||
"enabled": "resolveOrValue('acceleration_enabled') and ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"jerk_ironing":
|
||||
{
|
||||
"label": "Ironing Jerk",
|
||||
"description": "The maximum instantaneous velocity change while performing ironing.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "50",
|
||||
"default_value": 20,
|
||||
"value": "jerk_topbottom",
|
||||
"enabled": "resolveOrValue('jerk_enabled') and ironing_enabled",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
"enabled": "adaptive_layer_height_enabled",
|
||||
"default_value": 200.0,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "cartesio_extruder_0",
|
||||
"version": 2,
|
||||
"name": "Extruder 0",
|
||||
"name": "Extruder 1",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "cartesio",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "cartesio_extruder_1",
|
||||
"version": 2,
|
||||
"name": "Extruder 1",
|
||||
"name": "Extruder 2",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "cartesio",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "cartesio_extruder_2",
|
||||
"version": 2,
|
||||
"name": "Extruder 2",
|
||||
"name": "Extruder 3",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "cartesio",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "cartesio_extruder_3",
|
||||
"version": 2,
|
||||
"name": "Extruder 3",
|
||||
"name": "Extruder 4",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "cartesio",
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Cura 3.0\n"
|
||||
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
||||
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2017-11-21 16:58+0000\n"
|
||||
"PO-Revision-Date: 2017-08-11 14:30+0200\n"
|
||||
"Last-Translator: Bothof <info@bothof.nl>\n"
|
||||
"Language-Team: German\n"
|
||||
|
@ -6,8 +6,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Cura 3.0\n"
|
||||
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
||||
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2017-11-21 16:58+0000\n"
|
||||
"PO-Revision-Date: 2017-09-27 12:27+0200\n"
|
||||
"Last-Translator: Bothof <info@bothof.nl>\n"
|
||||
"Language-Team: German\n"
|
||||
@ -56,7 +56,9 @@ msgctxt "machine_start_gcode description"
|
||||
msgid ""
|
||||
"Gcode commands to be executed at the very start - separated by \n"
|
||||
"."
|
||||
msgstr "Gcode-Befehle, die zu Beginn ausgeführt werden sollen – getrennt durch \n."
|
||||
msgstr ""
|
||||
"Gcode-Befehle, die zu Beginn ausgeführt werden sollen – getrennt durch \n"
|
||||
"."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "machine_end_gcode label"
|
||||
@ -68,7 +70,9 @@ msgctxt "machine_end_gcode description"
|
||||
msgid ""
|
||||
"Gcode commands to be executed at the very end - separated by \n"
|
||||
"."
|
||||
msgstr "Gcode-Befehle, die Am Ende ausgeführt werden sollen – getrennt durch \n."
|
||||
msgstr ""
|
||||
"Gcode-Befehle, die Am Ende ausgeführt werden sollen – getrennt durch \n"
|
||||
"."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_guid label"
|
||||
@ -605,6 +609,31 @@ msgctxt "layer_height_0 description"
|
||||
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
|
||||
msgstr "Die Dicke der ersten Schicht in mm. Eine dicke erste Schicht erleichtert die Haftung am Druckbett."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance label"
|
||||
msgid "Slicing Tolerance"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance description"
|
||||
msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance option middle"
|
||||
msgid "Middle"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance option exclusive"
|
||||
msgid "Exclusive"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance option inclusive"
|
||||
msgid "Inclusive"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "line_width label"
|
||||
msgid "Line Width"
|
||||
@ -755,6 +784,16 @@ msgctxt "shell description"
|
||||
msgid "Shell"
|
||||
msgstr "Gehäuse"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_extruder_nr label"
|
||||
msgid "Wall Extruder"
|
||||
msgstr "Extruder für Wand"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_extruder_nr description"
|
||||
msgid "The extruder train used for printing the walls. This is used in multi-extrusion."
|
||||
msgstr "Die für das Drucken der Wände verwendete Extruder-Einheit. Diese wird für die Mehrfach-Extrusion benutzt."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_0_extruder_nr label"
|
||||
msgid "Outer Wall Extruder"
|
||||
@ -767,8 +806,8 @@ msgstr "Die für das Drucken der Außenwände verwendete Extruder-Einheit. Diese
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_x_extruder_nr label"
|
||||
msgid "Inner Walls Extruder"
|
||||
msgstr "Extruder Innenwände"
|
||||
msgid "Inner Wall Extruder"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_x_extruder_nr description"
|
||||
@ -1200,6 +1239,106 @@ msgctxt "skin_outline_count description"
|
||||
msgid "Replaces the outermost part of the top/bottom pattern with a number of concentric lines. Using one or two lines improves roofs that start on infill material."
|
||||
msgstr "Der äußerste Teil des oberen/unteren Musters wird durch eine Anzahl von konzentrischen Linien ersetzt. Die Verwendung von ein oder zwei Linien verbessert Dächer, die auf Füllmaterial beginnen."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled label"
|
||||
msgid "Enable Ironing"
|
||||
msgstr "Glätten aktivieren"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled description"
|
||||
msgid "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface."
|
||||
msgstr "Gehen Sie ein weiteres Mal über die Oberfläche, jedoch ohne Extrusionsmaterial. Damit wird der Kunststoff auf der Oberfläche weiter geschmolzen, was zu einer glatteren Oberfläche führt."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer label"
|
||||
msgid "Iron Only Highest Layer"
|
||||
msgstr "Nur oberste Schicht glätten"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer description"
|
||||
msgid "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish."
|
||||
msgstr "Führen Sie das Glätten nur für die allerletzte Schicht des Meshs aus. Dies spart Zeit, wenn die unteren Schichten keine glatte Oberflächenausführung erfordern."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern label"
|
||||
msgid "Ironing Pattern"
|
||||
msgstr "Glättungsmuster"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern description"
|
||||
msgid "The pattern to use for ironing top surfaces."
|
||||
msgstr "Das Muster, das für die Glättung der Oberflächen verwendet wird."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option concentric"
|
||||
msgid "Concentric"
|
||||
msgstr "Konzentrisch"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option zigzag"
|
||||
msgid "Zig Zag"
|
||||
msgstr "Zickzack"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing label"
|
||||
msgid "Ironing Line Spacing"
|
||||
msgstr "Glättungslinienabstand"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing description"
|
||||
msgid "The distance between the lines of ironing."
|
||||
msgstr "Der Abstand zwischen den Glättungslinien."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow label"
|
||||
msgid "Ironing Flow"
|
||||
msgstr "Glättungsfluss"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow description"
|
||||
msgid "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface."
|
||||
msgstr "Die Materialmenge relativ zu einer normalen Außenhautlinie, um während des Glättens zu extrudieren. Indem die Düse gefüllt bleibt, können einige Spalten in der oberen Schicht gefüllt werden, allerdings führt zu viel davon zu einer übermäßigen Extrudierung und Markierungen seitlich der Oberfläche."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset label"
|
||||
msgid "Ironing Inset"
|
||||
msgstr "Glättungseinsatz"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset description"
|
||||
msgid "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print."
|
||||
msgstr "Eine Distanz, die von den Kanten des Modells einzuhalten ist. Die Glättung des gesamten Weges zur Kante des Mesh führt möglicherweise zu einer gezackten Kante Ihres Drucks."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing label"
|
||||
msgid "Ironing Speed"
|
||||
msgstr "Glättungsgeschwindigkeit"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing description"
|
||||
msgid "The speed at which to pass over the top surface."
|
||||
msgstr "Die Geschwindigkeit, mit der über die Oberfläche gegangen wird."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing label"
|
||||
msgid "Ironing Acceleration"
|
||||
msgstr "Beschleunigung Glättung"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing description"
|
||||
msgid "The acceleration with which ironing is performed."
|
||||
msgstr "Die Beschleunigung, mit der das Glätten erfolgt."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing label"
|
||||
msgid "Ironing Jerk"
|
||||
msgstr "Ruckfunktion glätten"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing description"
|
||||
msgid "The maximum instantaneous velocity change while performing ironing."
|
||||
msgstr "Die maximale unmittelbare Geschwindigkeitsänderung während des Glättens."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill label"
|
||||
msgid "Infill"
|
||||
@ -1247,8 +1386,8 @@ msgstr "Füllmuster"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern description"
|
||||
msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, octet, quarter cubic and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
msgstr "Das Muster des Füllmaterials des Drucks. Die Linien- und Zickzackfüllmethode wechseln nach jeder Schicht die Richtung, um Materialkosten zu reduzieren. Die Gitter-, Dreieck- Würfel-, Viertelwürfel-, Octahedral- und konzentrischen Muster werden in jeder Schicht vollständig gedruckt. Würfel-, Viertelwürfel- und Octahedral-Füllungen wechseln mit jeder Schicht, um eine gleichmäßigere Verteilung der Stärke in allen Richtungen zu erzielen."
|
||||
msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern option grid"
|
||||
@ -1265,6 +1404,11 @@ msgctxt "infill_pattern option triangles"
|
||||
msgid "Triangles"
|
||||
msgstr "Dreiecke"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern option trihexagon"
|
||||
msgid "Tri-Hexagon"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern option cubic"
|
||||
msgid "Cubic"
|
||||
@ -1317,8 +1461,8 @@ msgstr "Füllungslinien verbinden"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "zig_zaggify_infill description"
|
||||
msgid "Connect the ends where the infill pattern meets the inner wall using a lines which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduces the effects on infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
|
||||
msgstr "Verbindet die Enden, an denen das Füllmuster auf die Innenwand trifft, mithilfe von Linien, die der Form der Innenwand folgen. Durch Aktivierung dieser Einstellung kann die Füllung besser an den Wänden haften; auch die Auswirkungen der Füllung auf die Qualität der vertikalen Flächen werden reduziert. Die Deaktivierung dieser Einstellung reduziert den Materialverbrauch."
|
||||
msgid "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_angles label"
|
||||
@ -1330,6 +1474,26 @@ msgctxt "infill_angles description"
|
||||
msgid "A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees for the lines and zig zag patterns and 45 degrees for all other patterns)."
|
||||
msgstr "Eine Liste von Ganzzahl-Linienrichtungen für die Verwendung. Elemente aus der Liste werden während des Aufbaus der Schichten sequentiell verwendet und wenn das Listenende erreicht wird, beginnt die Liste von vorne. Die Listenobjekte werden durch Kommas getrennt und die gesamte Liste ist in eckige Klammern gesetzt. Standardmäßig ist eine leere Liste vorhanden, was bedeutet, dass herkömmliche Standardwinkel (45- und 135-Grad für die Linien- und Zickzack-Muster und 45-Grad für alle anderen Muster) verwendet werden."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_x label"
|
||||
msgid "Infill X Offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_x description"
|
||||
msgid "The infill pattern is offset this distance along the X axis."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_y label"
|
||||
msgid "Infill Y Offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_y description"
|
||||
msgid "The infill pattern is offset this distance along the Y axis."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "sub_div_rad_add label"
|
||||
msgid "Cubic Subdivision Shell"
|
||||
@ -1640,6 +1804,26 @@ msgctxt "material_diameter description"
|
||||
msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
|
||||
msgstr "Der Durchmesser des verwendeten Filaments wird angepasst. Stellen Sie hier den Durchmesser des verwendeten Filaments ein."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_adhesion_tendency label"
|
||||
msgid "Adhesion Tendency"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_adhesion_tendency description"
|
||||
msgid "Surface adhesion tendency."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_surface_energy label"
|
||||
msgid "Surface Energy"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_surface_energy description"
|
||||
msgid "Surface energy."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_flow label"
|
||||
msgid "Flow"
|
||||
@ -2830,36 +3014,6 @@ msgctxt "support_connect_zigzags description"
|
||||
msgid "Connect the ZigZags. This will increase the strength of the zig zag support structure."
|
||||
msgstr "Die Zickzack-Elemente werden verbunden. Dies erhöht die Stärke der Zickzack-Stützstruktur."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags label"
|
||||
msgid "Break Up Support In Chunks"
|
||||
msgstr "Stützstruktur in Blöcke aufteilen"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags description"
|
||||
msgid "Skip some support line connections to make the support structure easier to break away. This setting is applicable to the Zig Zag support infill pattern."
|
||||
msgstr "Überspringen Sie einige Stützstruktur-Verbindungen, um das Brechen der Stützstruktur zu erleichtern. Diese Einstellung ist für die Zickzack-Stützstruktur-Füllung vorgesehen."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm label"
|
||||
msgid "Support Chunk Size"
|
||||
msgstr "Blockgröße für Stützstruktur"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm description"
|
||||
msgid "Leave out a connection between support lines once every N millimeter to make the support structure easier to break away."
|
||||
msgstr "Überspringen Sie eine Verbindung zwischen den Stützstrukturlinien nach jedem N-Millimeter, um das Brechen der Stützstruktur zu erleichtern."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count label"
|
||||
msgid "Support Chunk Line Count"
|
||||
msgstr "Anzahl der Stützstruktur-Blocklinien"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count description"
|
||||
msgid "Skip one in every N connection lines to make the support structure easier to break away."
|
||||
msgstr "Überspringen Sie eine in jeder N-Verbindungslinie, um das Wegbrechen der Stützstruktur zu erleichtern."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_infill_rate label"
|
||||
msgid "Support Density"
|
||||
@ -3399,8 +3553,8 @@ msgstr "Skirt-Abstand"
|
||||
msgctxt "skirt_gap description"
|
||||
msgid ""
|
||||
"The horizontal distance between the skirt and the first layer of the print.\n"
|
||||
"This is the minimum distance, multiple skirt lines will extend outwards from this distance."
|
||||
msgstr "Der horizontale Abstand zwischen dem Skirt und der ersten Schicht des Drucks.\nEs handelt sich dabei um den Mindestabstand. Ab diesem Abstand werden Skirt-Linien in äußerer Richtung angebracht."
|
||||
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "skirt_brim_minimal_length label"
|
||||
@ -3442,26 +3596,6 @@ msgctxt "brim_outside_only description"
|
||||
msgid "Only print the brim on the outside of the model. This reduces the amount of brim you need to remove afterwards, while it doesn't reduce the bed adhesion that much."
|
||||
msgstr "Brim nur an der Außenseite des Modells drucken. Damit reduziert sich die Anzahl der Brims, die Sie später entfernen müssen, während die Druckbetthaftung nicht signifikant eingeschränkt wird."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_layer_0 label"
|
||||
msgid "Initial Layer Z Offset"
|
||||
msgstr "Z-Versatz der ersten Schicht"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_layer_0 description"
|
||||
msgid "The extruder is offset from the normal height of the first layer by this amount. It can be positive (raised) or negative (lowered). Some filament types adhere to the build plate better if the extruder is raised slightly."
|
||||
msgstr "Der Extruder wird um diesen Wert von der normalen Höhe der ersten Schicht versetzt. Das kann positiv (erhöht) oder negativ (abgesenkt) erfolgen. Einige Filamenttypen haften besser am Druckbett, wenn der Extruder leicht erhöht ist."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_taper_layers label"
|
||||
msgid "Z Offset Taper Layers"
|
||||
msgstr "Z-Versatz Kegelschichten"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_taper_layers description"
|
||||
msgid "When non-zero, the Z offset is reduced to 0 over that many layers. A value of 0 means that the Z offset remains constant for all the layers in the print."
|
||||
msgstr "Bei Nicht-Null wird der Z-Versatz auf 0 über den zahlreichen Schichten reduziert. Ein Wert von 0 bedeutet, dass der Z-Versatz über alle Schichten des Drucks hinweg konstant bleibt."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "raft_margin label"
|
||||
msgid "Raft Extra Margin"
|
||||
@ -3479,8 +3613,8 @@ msgstr "Raft-Glättung"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "raft_smoothing description"
|
||||
msgid "This setting control how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle."
|
||||
msgstr "Diese Einstellung definiert, wie stark die Innenkanten des Raft-Umrisses gerundet werden. Die Innenkanten werden zu einem Halbkreis mit einem Radius entsprechend des hier definierten Werts gerundet. Diese Einstellung entfernt außerdem Löcher im Raft-Umriss, die kleiner als ein solcher Kreis sind."
|
||||
msgid "This setting controls how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "raft_airgap label"
|
||||
@ -3952,6 +4086,16 @@ msgctxt "meshfix_keep_open_polygons description"
|
||||
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
|
||||
msgstr "Normalerweise versucht Cura kleine Löcher im Netz abzudecken und Teile von Schichten, die große Löcher aufweisen, zu entfernen. Die Aktivierung dieser Option erhält jene Teile, die nicht abgedeckt werden können. Diese Option sollte nur als letzter Ausweg verwendet werden, wenn es ansonsten nicht möglich ist, einen korrekten G-Code zu berechnen."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "meshfix_maximum_resolution label"
|
||||
msgid "Maximum Resolution"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "meshfix_maximum_resolution description"
|
||||
msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "multiple_mesh_overlap label"
|
||||
msgid "Merged Meshes Overlap"
|
||||
@ -3982,6 +4126,16 @@ msgctxt "alternate_carve_order description"
|
||||
msgid "Switch to which mesh intersecting volumes will belong with every layer, so that the overlapping meshes become interwoven. Turning this setting off will cause one of the meshes to obtain all of the volume in the overlap, while it is removed from the other meshes."
|
||||
msgstr "Schaltet mit jeder Schicht das Volumen zu den entsprechenden Netzüberschneidungen, sodass die überlappenden Netze miteinander verwebt werden. Durch Abschalten dieser Funktion erhält eines der Netze das gesamte Volumen der Überlappung, während es von den anderen Netzen entfernt wird."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "remove_empty_first_layers label"
|
||||
msgid "Remove Empty First Layers"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "remove_empty_first_layers description"
|
||||
msgid "Remove empty layers beneath the first printed layer if they are present. Disabling this setting can cause empty first layers if the Slicing Tolerance setting is set to Exclusive or Middle."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "blackmagic label"
|
||||
msgid "Special Modes"
|
||||
@ -4187,6 +4341,36 @@ msgctxt "optimize_wall_printing_order description"
|
||||
msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization."
|
||||
msgstr "Optimieren Sie die Reihenfolge, in der die Wände gedruckt werden, um die Anzahl der Einzüge und die zurückgelegten Distanzen zu reduzieren. Dieser Schritt bringt für die meisten Teile Vorteile, allerdings werden einige möglicherweise länger benötigen. Vergleichen Sie deshalb bitte die Schätzung der Druckzeiten mit und ohne Optimierung."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags label"
|
||||
msgid "Break Up Support In Chunks"
|
||||
msgstr "Stützstruktur in Blöcke aufteilen"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags description"
|
||||
msgid "Skip some support line connections to make the support structure easier to break away. This setting is applicable to the Zig Zag support infill pattern."
|
||||
msgstr "Überspringen Sie einige Stützstruktur-Verbindungen, um das Brechen der Stützstruktur zu erleichtern. Diese Einstellung ist für die Zickzack-Stützstruktur-Füllung vorgesehen."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm label"
|
||||
msgid "Support Chunk Size"
|
||||
msgstr "Blockgröße für Stützstruktur"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm description"
|
||||
msgid "Leave out a connection between support lines once every N millimeter to make the support structure easier to break away."
|
||||
msgstr "Überspringen Sie eine Verbindung zwischen den Stützstrukturlinien nach jedem N-Millimeter, um das Brechen der Stützstruktur zu erleichtern."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count label"
|
||||
msgid "Support Chunk Line Count"
|
||||
msgstr "Anzahl der Stützstruktur-Blocklinien"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count description"
|
||||
msgid "Skip one in every N connection lines to make the support structure easier to break away."
|
||||
msgstr "Überspringen Sie eine in jeder N-Verbindungslinie, um das Wegbrechen der Stützstruktur zu erleichtern."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "draft_shield_enabled label"
|
||||
msgid "Enable Draft Shield"
|
||||
@ -4477,6 +4661,26 @@ msgctxt "magic_fuzzy_skin_point_dist description"
|
||||
msgid "The average distance between the random points introduced on each line segment. Note that the original points of the polygon are discarded, so a high smoothness results in a reduction of the resolution. This value must be higher than half the Fuzzy Skin Thickness."
|
||||
msgstr "Der durchschnittliche Abstand zwischen den willkürlich auf jedes Liniensegment aufgebrachten Punkten. Beachten Sie, dass die Originalpunkte des Polygons verworfen werden, sodass eine hohe Glättung in einer Reduzierung der Auflösung resultiert. Dieser Wert muss größer sein als die Hälfte der Dicke der ungleichmäßigen Außenhaut."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_max_extrusion_offset label"
|
||||
msgid "Flow rate compensation max extrusion offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_max_extrusion_offset description"
|
||||
msgid "The maximum distance in mm to compensate."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_extrusion_offset_factor label"
|
||||
msgid "Flow rate compensation factor"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_extrusion_offset_factor description"
|
||||
msgid "The multiplication factor for the flow rate -> distance translation."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wireframe_enabled label"
|
||||
msgid "Wire Printing"
|
||||
@ -4627,7 +4831,9 @@ msgctxt "wireframe_up_half_speed description"
|
||||
msgid ""
|
||||
"Distance of an upward move which is extruded with half speed.\n"
|
||||
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
|
||||
msgstr "Die Strecke einer Aufwärtsbewegung, die mit halber Geschwindigkeit extrudiert wird.\nDies kann zu einer besseren Haftung an vorhergehenden Schichten führen, während gleichzeitig ein Überhitzen des Materials in diesen Schichten vermieden wird. Dies gilt nur für das Drucken mit Drahtstruktur."
|
||||
msgstr ""
|
||||
"Die Strecke einer Aufwärtsbewegung, die mit halber Geschwindigkeit extrudiert wird.\n"
|
||||
"Dies kann zu einer besseren Haftung an vorhergehenden Schichten führen, während gleichzeitig ein Überhitzen des Materials in diesen Schichten vermieden wird. Dies gilt nur für das Drucken mit Drahtstruktur."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wireframe_top_jump label"
|
||||
@ -4734,106 +4940,6 @@ msgctxt "wireframe_nozzle_clearance description"
|
||||
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
|
||||
msgstr "Der Abstand zwischen der Düse und den horizontalen Abwärtslinien. Bei einem größeren Abstand haben die diagonalen Abwärtslinien einen weniger spitzen Winkel, was wiederum weniger Aufwärtsverbindungen zur nächsten Schicht zur Folge hat. Dies gilt nur für das Drucken mit Drahtstruktur."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled label"
|
||||
msgid "Enable Ironing"
|
||||
msgstr "Glätten aktivieren"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled description"
|
||||
msgid "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface."
|
||||
msgstr "Gehen Sie ein weiteres Mal über die Oberfläche, jedoch ohne Extrusionsmaterial. Damit wird der Kunststoff auf der Oberfläche weiter geschmolzen, was zu einer glatteren Oberfläche führt."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer label"
|
||||
msgid "Iron Only Highest Layer"
|
||||
msgstr "Nur oberste Schicht glätten"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer description"
|
||||
msgid "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish."
|
||||
msgstr "Führen Sie das Glätten nur für die allerletzte Schicht des Meshs aus. Dies spart Zeit, wenn die unteren Schichten keine glatte Oberflächenausführung erfordern."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern label"
|
||||
msgid "Ironing Pattern"
|
||||
msgstr "Glättungsmuster"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern description"
|
||||
msgid "The pattern to use for ironing top surfaces."
|
||||
msgstr "Das Muster, das für die Glättung der Oberflächen verwendet wird."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option concentric"
|
||||
msgid "Concentric"
|
||||
msgstr "Konzentrisch"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option zigzag"
|
||||
msgid "Zig Zag"
|
||||
msgstr "Zickzack"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing label"
|
||||
msgid "Ironing Line Spacing"
|
||||
msgstr "Glättungslinienabstand"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing description"
|
||||
msgid "The distance between the lines of ironing."
|
||||
msgstr "Der Abstand zwischen den Glättungslinien."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow label"
|
||||
msgid "Ironing Flow"
|
||||
msgstr "Glättungsfluss"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow description"
|
||||
msgid "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface."
|
||||
msgstr "Die Materialmenge relativ zu einer normalen Außenhautlinie, um während des Glättens zu extrudieren. Indem die Düse gefüllt bleibt, können einige Spalten in der oberen Schicht gefüllt werden, allerdings führt zu viel davon zu einer übermäßigen Extrudierung und Markierungen seitlich der Oberfläche."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset label"
|
||||
msgid "Ironing Inset"
|
||||
msgstr "Glättungseinsatz"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset description"
|
||||
msgid "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print."
|
||||
msgstr "Eine Distanz, die von den Kanten des Modells einzuhalten ist. Die Glättung des gesamten Weges zur Kante des Mesh führt möglicherweise zu einer gezackten Kante Ihres Drucks."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing label"
|
||||
msgid "Ironing Speed"
|
||||
msgstr "Glättungsgeschwindigkeit"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing description"
|
||||
msgid "The speed at which to pass over the top surface."
|
||||
msgstr "Die Geschwindigkeit, mit der über die Oberfläche gegangen wird."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing label"
|
||||
msgid "Ironing Acceleration"
|
||||
msgstr "Beschleunigung Glättung"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing description"
|
||||
msgid "The acceleration with which ironing is performed."
|
||||
msgstr "Die Beschleunigung, mit der das Glätten erfolgt."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing label"
|
||||
msgid "Ironing Jerk"
|
||||
msgstr "Ruckfunktion glätten"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing description"
|
||||
msgid "The maximum instantaneous velocity change while performing ironing."
|
||||
msgstr "Die maximale unmittelbare Geschwindigkeitsänderung während des Glättens."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "command_line_settings label"
|
||||
msgid "Command Line Settings"
|
||||
@ -4894,13 +5000,45 @@ msgctxt "mesh_rotation_matrix description"
|
||||
msgid "Transformation matrix to be applied to the model when loading it from file."
|
||||
msgstr "Transformationsmatrix, die beim Laden aus der Datei auf das Modell angewandt wird."
|
||||
|
||||
#~ msgctxt "wall_extruder_nr label"
|
||||
#~ msgid "Wall Extruder"
|
||||
#~ msgstr "Extruder für Wand"
|
||||
#~ msgctxt "wall_x_extruder_nr label"
|
||||
#~ msgid "Inner Walls Extruder"
|
||||
#~ msgstr "Extruder Innenwände"
|
||||
|
||||
#~ msgctxt "wall_extruder_nr description"
|
||||
#~ msgid "The extruder train used for printing the walls. This is used in multi-extrusion."
|
||||
#~ msgstr "Die für das Drucken der Wände verwendete Extruder-Einheit. Diese wird für die Mehrfach-Extrusion benutzt."
|
||||
#~ msgctxt "infill_pattern description"
|
||||
#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, octet, quarter cubic and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
#~ msgstr "Das Muster des Füllmaterials des Drucks. Die Linien- und Zickzackfüllmethode wechseln nach jeder Schicht die Richtung, um Materialkosten zu reduzieren. Die Gitter-, Dreieck- Würfel-, Viertelwürfel-, Octahedral- und konzentrischen Muster werden in jeder Schicht vollständig gedruckt. Würfel-, Viertelwürfel- und Octahedral-Füllungen wechseln mit jeder Schicht, um eine gleichmäßigere Verteilung der Stärke in allen Richtungen zu erzielen."
|
||||
|
||||
#~ msgctxt "zig_zaggify_infill description"
|
||||
#~ msgid "Connect the ends where the infill pattern meets the inner wall using a lines which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduces the effects on infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
|
||||
#~ msgstr "Verbindet die Enden, an denen das Füllmuster auf die Innenwand trifft, mithilfe von Linien, die der Form der Innenwand folgen. Durch Aktivierung dieser Einstellung kann die Füllung besser an den Wänden haften; auch die Auswirkungen der Füllung auf die Qualität der vertikalen Flächen werden reduziert. Die Deaktivierung dieser Einstellung reduziert den Materialverbrauch."
|
||||
|
||||
#~ msgctxt "skirt_gap description"
|
||||
#~ msgid ""
|
||||
#~ "The horizontal distance between the skirt and the first layer of the print.\n"
|
||||
#~ "This is the minimum distance, multiple skirt lines will extend outwards from this distance."
|
||||
#~ msgstr ""
|
||||
#~ "Der horizontale Abstand zwischen dem Skirt und der ersten Schicht des Drucks.\n"
|
||||
#~ "Es handelt sich dabei um den Mindestabstand. Ab diesem Abstand werden Skirt-Linien in äußerer Richtung angebracht."
|
||||
|
||||
#~ msgctxt "z_offset_layer_0 label"
|
||||
#~ msgid "Initial Layer Z Offset"
|
||||
#~ msgstr "Z-Versatz der ersten Schicht"
|
||||
|
||||
#~ msgctxt "z_offset_layer_0 description"
|
||||
#~ msgid "The extruder is offset from the normal height of the first layer by this amount. It can be positive (raised) or negative (lowered). Some filament types adhere to the build plate better if the extruder is raised slightly."
|
||||
#~ msgstr "Der Extruder wird um diesen Wert von der normalen Höhe der ersten Schicht versetzt. Das kann positiv (erhöht) oder negativ (abgesenkt) erfolgen. Einige Filamenttypen haften besser am Druckbett, wenn der Extruder leicht erhöht ist."
|
||||
|
||||
#~ msgctxt "z_offset_taper_layers label"
|
||||
#~ msgid "Z Offset Taper Layers"
|
||||
#~ msgstr "Z-Versatz Kegelschichten"
|
||||
|
||||
#~ msgctxt "z_offset_taper_layers description"
|
||||
#~ msgid "When non-zero, the Z offset is reduced to 0 over that many layers. A value of 0 means that the Z offset remains constant for all the layers in the print."
|
||||
#~ msgstr "Bei Nicht-Null wird der Z-Versatz auf 0 über den zahlreichen Schichten reduziert. Ein Wert von 0 bedeutet, dass der Z-Versatz über alle Schichten des Drucks hinweg konstant bleibt."
|
||||
|
||||
#~ msgctxt "raft_smoothing description"
|
||||
#~ msgid "This setting control how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle."
|
||||
#~ msgstr "Diese Einstellung definiert, wie stark die Innenkanten des Raft-Umrisses gerundet werden. Die Innenkanten werden zu einem Halbkreis mit einem Radius entsprechend des hier definierten Werts gerundet. Diese Einstellung entfernt außerdem Löcher im Raft-Umriss, die kleiner als ein solcher Kreis sind."
|
||||
|
||||
#~ msgctxt "infill_pattern description"
|
||||
#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, tetrahedral and concentric patterns are fully printed every layer. Cubic and tetrahedral infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Cura 3.0\n"
|
||||
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
||||
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2017-11-21 16:58+0000\n"
|
||||
"PO-Revision-Date: 2017-08-11 14:31+0200\n"
|
||||
"Last-Translator: Bothof <info@bothof.nl>\n"
|
||||
"Language-Team: Spanish\n"
|
||||
|
@ -6,8 +6,8 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Cura 3.0\n"
|
||||
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
||||
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
|
||||
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
|
||||
"POT-Creation-Date: 2017-11-21 16:58+0000\n"
|
||||
"PO-Revision-Date: 2017-09-27 12:27+0200\n"
|
||||
"Last-Translator: Bothof <info@bothof.nl>\n"
|
||||
"Language-Team: Spanish\n"
|
||||
@ -56,7 +56,9 @@ msgctxt "machine_start_gcode description"
|
||||
msgid ""
|
||||
"Gcode commands to be executed at the very start - separated by \n"
|
||||
"."
|
||||
msgstr "Los comandos de Gcode que se ejecutarán justo al inicio - separados por \n."
|
||||
msgstr ""
|
||||
"Los comandos de Gcode que se ejecutarán justo al inicio - separados por \n"
|
||||
"."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "machine_end_gcode label"
|
||||
@ -68,7 +70,9 @@ msgctxt "machine_end_gcode description"
|
||||
msgid ""
|
||||
"Gcode commands to be executed at the very end - separated by \n"
|
||||
"."
|
||||
msgstr "Los comandos de Gcode que se ejecutarán justo al final - separados por \n."
|
||||
msgstr ""
|
||||
"Los comandos de Gcode que se ejecutarán justo al final - separados por \n"
|
||||
"."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_guid label"
|
||||
@ -605,6 +609,31 @@ msgctxt "layer_height_0 description"
|
||||
msgid "The height of the initial layer in mm. A thicker initial layer makes adhesion to the build plate easier."
|
||||
msgstr "Altura de capa inicial en mm. Una capa inicial más gruesa se adhiere a la placa de impresión con mayor facilidad."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance label"
|
||||
msgid "Slicing Tolerance"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance description"
|
||||
msgid "How to slice layers with diagonal surfaces. The areas of a layer can be generated based on where the middle of the layer intersects the surface (Middle). Alternatively each layer can have the areas which fall inside of the volume throughout the height of the layer (Exclusive) or a layer has the areas which fall inside anywhere within the layer (Inclusive). Exclusive retains the most details, Inclusive makes for the best fit and Middle takes the least time to process."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance option middle"
|
||||
msgid "Middle"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance option exclusive"
|
||||
msgid "Exclusive"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "slicing_tolerance option inclusive"
|
||||
msgid "Inclusive"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "line_width label"
|
||||
msgid "Line Width"
|
||||
@ -755,6 +784,16 @@ msgctxt "shell description"
|
||||
msgid "Shell"
|
||||
msgstr "Perímetro"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_extruder_nr label"
|
||||
msgid "Wall Extruder"
|
||||
msgstr "Extrusor de pared"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_extruder_nr description"
|
||||
msgid "The extruder train used for printing the walls. This is used in multi-extrusion."
|
||||
msgstr "El tren extrusor que se utiliza para imprimir paredes. Se emplea en la extrusión múltiple."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_0_extruder_nr label"
|
||||
msgid "Outer Wall Extruder"
|
||||
@ -767,8 +806,8 @@ msgstr "El tren extrusor que se utiliza para imprimir la pared exterior. Se empl
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_x_extruder_nr label"
|
||||
msgid "Inner Walls Extruder"
|
||||
msgstr "Extrusor de paredes interiores"
|
||||
msgid "Inner Wall Extruder"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wall_x_extruder_nr description"
|
||||
@ -1200,6 +1239,106 @@ msgctxt "skin_outline_count description"
|
||||
msgid "Replaces the outermost part of the top/bottom pattern with a number of concentric lines. Using one or two lines improves roofs that start on infill material."
|
||||
msgstr "Reemplaza la parte más externa del patrón superior/inferior con un número de líneas concéntricas. Mediante el uso de una o dos líneas mejora los techos que comienzan en el material de relleno."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled label"
|
||||
msgid "Enable Ironing"
|
||||
msgstr "Habilitar alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled description"
|
||||
msgid "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface."
|
||||
msgstr "Pasar por la superficie superior una vez más, pero sin extruir material, para derretir la parte externa del plástico y crear una superficie más lisa."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer label"
|
||||
msgid "Iron Only Highest Layer"
|
||||
msgstr "Planchar solo la capa superior"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer description"
|
||||
msgid "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish."
|
||||
msgstr "Planchar únicamente la última capa de la malla. De este modo se ahorra tiempo si las capas inferiores no requieren un acabado superficial suave."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern label"
|
||||
msgid "Ironing Pattern"
|
||||
msgstr "Patrón de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern description"
|
||||
msgid "The pattern to use for ironing top surfaces."
|
||||
msgstr "El patrón que se usará para el alisado de las superficies superiores."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option concentric"
|
||||
msgid "Concentric"
|
||||
msgstr "Concéntrico"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option zigzag"
|
||||
msgid "Zig Zag"
|
||||
msgstr "Zigzag"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing label"
|
||||
msgid "Ironing Line Spacing"
|
||||
msgstr "Espaciado de líneas del alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing description"
|
||||
msgid "The distance between the lines of ironing."
|
||||
msgstr "Distancia entre las líneas del alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow label"
|
||||
msgid "Ironing Flow"
|
||||
msgstr "Flujo de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow description"
|
||||
msgid "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface."
|
||||
msgstr "Cantidad de material (relativa a la línea del forro normal) que se extruye durante el alisado. Dejar la tobera llena permite rellenar algunas grietas de la superficie, pero llenarla demasiado puede provocar la sobrextrusión y afectar a la superficie."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset label"
|
||||
msgid "Ironing Inset"
|
||||
msgstr "Inserción de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset description"
|
||||
msgid "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print."
|
||||
msgstr "Distancia que debe guardarse desde el borde del modelo. Si se alisa hasta el borde de la malla, puede quedar un borde irregular."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing label"
|
||||
msgid "Ironing Speed"
|
||||
msgstr "Velocidad de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing description"
|
||||
msgid "The speed at which to pass over the top surface."
|
||||
msgstr "Velocidad a la que pasa por encima de la superficie superior."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing label"
|
||||
msgid "Ironing Acceleration"
|
||||
msgstr "Aceleración del alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing description"
|
||||
msgid "The acceleration with which ironing is performed."
|
||||
msgstr "La aceleración a la que se produce el alisado."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing label"
|
||||
msgid "Ironing Jerk"
|
||||
msgstr "Impulso de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing description"
|
||||
msgid "The maximum instantaneous velocity change while performing ironing."
|
||||
msgstr "Cambio en la velocidad instantánea máxima durante el alisado."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill label"
|
||||
msgid "Infill"
|
||||
@ -1247,8 +1386,8 @@ msgstr "Patrón de relleno"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern description"
|
||||
msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, octet, quarter cubic and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
msgstr "Patrón del material de relleno de la impresión. El relleno de línea y zigzag cambia de dirección en capas alternas, con lo que se reduce el coste del material. Los patrones de rejilla, triángulo, cúbico, de octeto, cúbico bitruncado y concéntrico se imprimen en todas las capas por completo. El relleno cúbico, cúbico bitruncado y de octeto cambian en cada capa para proporcionar una distribución de fuerza equitativa en cada dirección."
|
||||
msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, tri-hexagon, cubic, octet, quarter cubic, cross and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern option grid"
|
||||
@ -1265,6 +1404,11 @@ msgctxt "infill_pattern option triangles"
|
||||
msgid "Triangles"
|
||||
msgstr "Triángulos"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern option trihexagon"
|
||||
msgid "Tri-Hexagon"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_pattern option cubic"
|
||||
msgid "Cubic"
|
||||
@ -1317,8 +1461,8 @@ msgstr "Conectar líneas de relleno"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "zig_zaggify_infill description"
|
||||
msgid "Connect the ends where the infill pattern meets the inner wall using a lines which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduces the effects on infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
|
||||
msgstr "Conectar los extremos donde los patrones de relleno se juntan con la pared interior usando una línea que siga la forma de esta. Habilitar este ajuste puede lograr que el relleno se adhiera mejor a las paredes y reduce el efecto del relleno sobre la calidad de las superficies verticales. Deshabilitar este ajuste reduce la cantidad de material utilizado."
|
||||
msgid "Connect the ends where the infill pattern meets the inner wall using a line which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduce the effects of infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_angles label"
|
||||
@ -1330,6 +1474,26 @@ msgctxt "infill_angles description"
|
||||
msgid "A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees for the lines and zig zag patterns and 45 degrees for all other patterns)."
|
||||
msgstr "Una lista de los valores enteros de las direcciones de línea. Los elementos de esta lista se utilizan de forma secuencial a medida que las capas se utilizan y, cuando se alcanza el final, la lista vuelve a comenzar desde el principio. Los elementos de la lista están separados por comas y toda la lista aparece entre corchetes. El valor predeterminado es una lista vacía que utiliza los ángulos predeterminados típicos (45 y 135 grados para las líneas y los patrones en zigzag y 45 grados para el resto de patrones)."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_x label"
|
||||
msgid "Infill X Offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_x description"
|
||||
msgid "The infill pattern is offset this distance along the X axis."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_y label"
|
||||
msgid "Infill Y Offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "infill_offset_y description"
|
||||
msgid "The infill pattern is offset this distance along the Y axis."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "sub_div_rad_add label"
|
||||
msgid "Cubic Subdivision Shell"
|
||||
@ -1640,6 +1804,26 @@ msgctxt "material_diameter description"
|
||||
msgid "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament."
|
||||
msgstr "Ajusta el diámetro del filamento utilizado. Este valor debe coincidir con el diámetro del filamento utilizado."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_adhesion_tendency label"
|
||||
msgid "Adhesion Tendency"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_adhesion_tendency description"
|
||||
msgid "Surface adhesion tendency."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_surface_energy label"
|
||||
msgid "Surface Energy"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_surface_energy description"
|
||||
msgid "Surface energy."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "material_flow label"
|
||||
msgid "Flow"
|
||||
@ -2830,36 +3014,6 @@ msgctxt "support_connect_zigzags description"
|
||||
msgid "Connect the ZigZags. This will increase the strength of the zig zag support structure."
|
||||
msgstr "Conectar los zigzags. Esto aumentará la resistencia de la estructura del soporte de zigzag."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags label"
|
||||
msgid "Break Up Support In Chunks"
|
||||
msgstr "Descomponer el soporte en pedazos"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags description"
|
||||
msgid "Skip some support line connections to make the support structure easier to break away. This setting is applicable to the Zig Zag support infill pattern."
|
||||
msgstr "Omitir algunas conexiones de línea de soporte para que la estructura de soporte sea más fácil de descomponer. Este ajuste es aplicable al patrón de relleno del soporte en zigzag."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm label"
|
||||
msgid "Support Chunk Size"
|
||||
msgstr "Tamaño de los pedazos de soporte"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm description"
|
||||
msgid "Leave out a connection between support lines once every N millimeter to make the support structure easier to break away."
|
||||
msgstr "Omitir una conexión entre líneas de soporte una vez cada N milímetros a fin de lograr que la estructura de soporte resulte más fácil de descomponer."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count label"
|
||||
msgid "Support Chunk Line Count"
|
||||
msgstr "Recuento de líneas de pedazos del soporte"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count description"
|
||||
msgid "Skip one in every N connection lines to make the support structure easier to break away."
|
||||
msgstr "Omitir una de cada N líneas de conexión para que la estructura de soporte se descomponga fácilmente."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_infill_rate label"
|
||||
msgid "Support Density"
|
||||
@ -3399,8 +3553,8 @@ msgstr "Distancia de falda"
|
||||
msgctxt "skirt_gap description"
|
||||
msgid ""
|
||||
"The horizontal distance between the skirt and the first layer of the print.\n"
|
||||
"This is the minimum distance, multiple skirt lines will extend outwards from this distance."
|
||||
msgstr "La distancia horizontal entre la falda y la primera capa de la impresión.\nEsta es la distancia mínima; múltiples líneas de falda se extenderán hacia el exterior a partir de esta distancia."
|
||||
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "skirt_brim_minimal_length label"
|
||||
@ -3442,26 +3596,6 @@ msgctxt "brim_outside_only description"
|
||||
msgid "Only print the brim on the outside of the model. This reduces the amount of brim you need to remove afterwards, while it doesn't reduce the bed adhesion that much."
|
||||
msgstr "Imprimir solo el borde en el exterior del modelo. Esto reduce el número de bordes que deberá retirar después sin que la adherencia a la plataforma se vea muy afectada."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_layer_0 label"
|
||||
msgid "Initial Layer Z Offset"
|
||||
msgstr "Desplazamiento Z de la capa inicial"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_layer_0 description"
|
||||
msgid "The extruder is offset from the normal height of the first layer by this amount. It can be positive (raised) or negative (lowered). Some filament types adhere to the build plate better if the extruder is raised slightly."
|
||||
msgstr "El extrusor se desplaza de la altura normal de la primera capa con este valor, el cual puede ser positivo (elevado) o negativo (bajo). Algunas clases de filamentos se adhieren mejor a la placa de impresión si se levanta ligeramente el extrusor."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_taper_layers label"
|
||||
msgid "Z Offset Taper Layers"
|
||||
msgstr "Desplazamiento Z de capas en disminución"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "z_offset_taper_layers description"
|
||||
msgid "When non-zero, the Z offset is reduced to 0 over that many layers. A value of 0 means that the Z offset remains constant for all the layers in the print."
|
||||
msgstr "Si no es cero, el desplazamiento Z se reduce a cero en las capas. Un valor de cero implica que el desplazamiento Z se mantiene constante en todas las capas de impresión."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "raft_margin label"
|
||||
msgid "Raft Extra Margin"
|
||||
@ -3479,8 +3613,8 @@ msgstr "Suavizado de la balsa"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "raft_smoothing description"
|
||||
msgid "This setting control how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle."
|
||||
msgstr "Este ajuste controla la medida en que se redondean las esquinas interiores en el contorno de la balsa. Las esquinas hacia el interior se redondean en semicírculo con un radio equivalente al valor aquí indicado. Este ajuste también elimina los orificios del contorno de la balsa que sean más pequeños que dicho círculo."
|
||||
msgid "This setting controls how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "raft_airgap label"
|
||||
@ -3952,6 +4086,16 @@ msgctxt "meshfix_keep_open_polygons description"
|
||||
msgid "Normally Cura tries to stitch up small holes in the mesh and remove parts of a layer with big holes. Enabling this option keeps those parts which cannot be stitched. This option should be used as a last resort option when everything else fails to produce proper GCode."
|
||||
msgstr "Normalmente, Cura intenta coser los pequeños agujeros de la malla y eliminar las partes de una capa con grandes agujeros. Al habilitar esta opción se mantienen aquellas partes que no puedan coserse. Esta opción se debe utilizar como una opción de último recurso cuando todo lo demás falla para producir un GCode adecuado."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "meshfix_maximum_resolution label"
|
||||
msgid "Maximum Resolution"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "meshfix_maximum_resolution description"
|
||||
msgid "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "multiple_mesh_overlap label"
|
||||
msgid "Merged Meshes Overlap"
|
||||
@ -3982,6 +4126,16 @@ msgctxt "alternate_carve_order description"
|
||||
msgid "Switch to which mesh intersecting volumes will belong with every layer, so that the overlapping meshes become interwoven. Turning this setting off will cause one of the meshes to obtain all of the volume in the overlap, while it is removed from the other meshes."
|
||||
msgstr "Cambiar la malla a la que pertenecerán los volúmenes que se cruzan en cada capa, de forma que las mallas superpuestas se entrelacen. Desactivar esta opción dará lugar a que una de las mallas reciba todo el volumen de la superposición y que este se elimine de las demás mallas."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "remove_empty_first_layers label"
|
||||
msgid "Remove Empty First Layers"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "remove_empty_first_layers description"
|
||||
msgid "Remove empty layers beneath the first printed layer if they are present. Disabling this setting can cause empty first layers if the Slicing Tolerance setting is set to Exclusive or Middle."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "blackmagic label"
|
||||
msgid "Special Modes"
|
||||
@ -4187,6 +4341,36 @@ msgctxt "optimize_wall_printing_order description"
|
||||
msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization."
|
||||
msgstr "Optimizar el orden en el que se imprimen las paredes a fin de reducir el número de retracciones y la distancia recorrida. La mayoría de los componentes se beneficiarán si este ajuste está habilitado pero, en algunos casos, se puede tardar más, por lo que deben compararse las previsiones de tiempo de impresión con y sin optimización."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags label"
|
||||
msgid "Break Up Support In Chunks"
|
||||
msgstr "Descomponer el soporte en pedazos"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_some_zags description"
|
||||
msgid "Skip some support line connections to make the support structure easier to break away. This setting is applicable to the Zig Zag support infill pattern."
|
||||
msgstr "Omitir algunas conexiones de línea de soporte para que la estructura de soporte sea más fácil de descomponer. Este ajuste es aplicable al patrón de relleno del soporte en zigzag."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm label"
|
||||
msgid "Support Chunk Size"
|
||||
msgstr "Tamaño de los pedazos de soporte"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_skip_zag_per_mm description"
|
||||
msgid "Leave out a connection between support lines once every N millimeter to make the support structure easier to break away."
|
||||
msgstr "Omitir una conexión entre líneas de soporte una vez cada N milímetros a fin de lograr que la estructura de soporte resulte más fácil de descomponer."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count label"
|
||||
msgid "Support Chunk Line Count"
|
||||
msgstr "Recuento de líneas de pedazos del soporte"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "support_zag_skip_count description"
|
||||
msgid "Skip one in every N connection lines to make the support structure easier to break away."
|
||||
msgstr "Omitir una de cada N líneas de conexión para que la estructura de soporte se descomponga fácilmente."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "draft_shield_enabled label"
|
||||
msgid "Enable Draft Shield"
|
||||
@ -4477,6 +4661,26 @@ msgctxt "magic_fuzzy_skin_point_dist description"
|
||||
msgid "The average distance between the random points introduced on each line segment. Note that the original points of the polygon are discarded, so a high smoothness results in a reduction of the resolution. This value must be higher than half the Fuzzy Skin Thickness."
|
||||
msgstr "Distancia media entre los puntos aleatorios introducidos en cada segmento de línea. Tenga en cuenta que los puntos originales del polígono se descartan, así que un suavizado alto produce una reducción de la resolución. Este valor debe ser mayor que la mitad del grosor del forro difuso."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_max_extrusion_offset label"
|
||||
msgid "Flow rate compensation max extrusion offset"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_max_extrusion_offset description"
|
||||
msgid "The maximum distance in mm to compensate."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_extrusion_offset_factor label"
|
||||
msgid "Flow rate compensation factor"
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "flow_rate_extrusion_offset_factor description"
|
||||
msgid "The multiplication factor for the flow rate -> distance translation."
|
||||
msgstr ""
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wireframe_enabled label"
|
||||
msgid "Wire Printing"
|
||||
@ -4627,7 +4831,9 @@ msgctxt "wireframe_up_half_speed description"
|
||||
msgid ""
|
||||
"Distance of an upward move which is extruded with half speed.\n"
|
||||
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
|
||||
msgstr "Distancia de un movimiento ascendente que se extrude a media velocidad.\nEsto puede causar una mejor adherencia a las capas anteriores, aunque no calienta demasiado el material en esas capas. Solo se aplica a la impresión de alambre."
|
||||
msgstr ""
|
||||
"Distancia de un movimiento ascendente que se extrude a media velocidad.\n"
|
||||
"Esto puede causar una mejor adherencia a las capas anteriores, aunque no calienta demasiado el material en esas capas. Solo se aplica a la impresión de alambre."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "wireframe_top_jump label"
|
||||
@ -4734,106 +4940,6 @@ msgctxt "wireframe_nozzle_clearance description"
|
||||
msgid "Distance between the nozzle and horizontally downward lines. Larger clearance results in diagonally downward lines with a less steep angle, which in turn results in less upward connections with the next layer. Only applies to Wire Printing."
|
||||
msgstr "Distancia entre la tobera y líneas descendentes en horizontal. Cuanto mayor sea la holgura, menos pronunciado será el ángulo de las líneas descendentes en diagonal, lo que a su vez se traduce en menos conexiones ascendentes con la siguiente capa. Solo se aplica a la impresión de alambre."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled label"
|
||||
msgid "Enable Ironing"
|
||||
msgstr "Habilitar alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_enabled description"
|
||||
msgid "Go over the top surface one additional time, but without extruding material. This is meant to melt the plastic on top further, creating a smoother surface."
|
||||
msgstr "Pasar por la superficie superior una vez más, pero sin extruir material, para derretir la parte externa del plástico y crear una superficie más lisa."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer label"
|
||||
msgid "Iron Only Highest Layer"
|
||||
msgstr "Planchar solo la capa superior"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_only_highest_layer description"
|
||||
msgid "Only perform ironing on the very last layer of the mesh. This saves time if the lower layers don't need a smooth surface finish."
|
||||
msgstr "Planchar únicamente la última capa de la malla. De este modo se ahorra tiempo si las capas inferiores no requieren un acabado superficial suave."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern label"
|
||||
msgid "Ironing Pattern"
|
||||
msgstr "Patrón de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern description"
|
||||
msgid "The pattern to use for ironing top surfaces."
|
||||
msgstr "El patrón que se usará para el alisado de las superficies superiores."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option concentric"
|
||||
msgid "Concentric"
|
||||
msgstr "Concéntrico"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_pattern option zigzag"
|
||||
msgid "Zig Zag"
|
||||
msgstr "Zigzag"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing label"
|
||||
msgid "Ironing Line Spacing"
|
||||
msgstr "Espaciado de líneas del alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_line_spacing description"
|
||||
msgid "The distance between the lines of ironing."
|
||||
msgstr "Distancia entre las líneas del alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow label"
|
||||
msgid "Ironing Flow"
|
||||
msgstr "Flujo de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_flow description"
|
||||
msgid "The amount of material, relative to a normal skin line, to extrude during ironing. Keeping the nozzle filled helps filling some of the crevices of the top surface, but too much results in overextrusion and blips on the side of the surface."
|
||||
msgstr "Cantidad de material (relativa a la línea del forro normal) que se extruye durante el alisado. Dejar la tobera llena permite rellenar algunas grietas de la superficie, pero llenarla demasiado puede provocar la sobrextrusión y afectar a la superficie."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset label"
|
||||
msgid "Ironing Inset"
|
||||
msgstr "Inserción de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "ironing_inset description"
|
||||
msgid "A distance to keep from the edges of the model. Ironing all the way to the edge of the mesh may result in a jagged edge on your print."
|
||||
msgstr "Distancia que debe guardarse desde el borde del modelo. Si se alisa hasta el borde de la malla, puede quedar un borde irregular."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing label"
|
||||
msgid "Ironing Speed"
|
||||
msgstr "Velocidad de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "speed_ironing description"
|
||||
msgid "The speed at which to pass over the top surface."
|
||||
msgstr "Velocidad a la que pasa por encima de la superficie superior."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing label"
|
||||
msgid "Ironing Acceleration"
|
||||
msgstr "Aceleración del alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "acceleration_ironing description"
|
||||
msgid "The acceleration with which ironing is performed."
|
||||
msgstr "La aceleración a la que se produce el alisado."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing label"
|
||||
msgid "Ironing Jerk"
|
||||
msgstr "Impulso de alisado"
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "jerk_ironing description"
|
||||
msgid "The maximum instantaneous velocity change while performing ironing."
|
||||
msgstr "Cambio en la velocidad instantánea máxima durante el alisado."
|
||||
|
||||
#: fdmprinter.def.json
|
||||
msgctxt "command_line_settings label"
|
||||
msgid "Command Line Settings"
|
||||
@ -4894,13 +5000,45 @@ msgctxt "mesh_rotation_matrix description"
|
||||
msgid "Transformation matrix to be applied to the model when loading it from file."
|
||||
msgstr "Matriz de transformación que se aplicará al modelo cuando se cargue desde el archivo."
|
||||
|
||||
#~ msgctxt "wall_extruder_nr label"
|
||||
#~ msgid "Wall Extruder"
|
||||
#~ msgstr "Extrusor de pared"
|
||||
#~ msgctxt "wall_x_extruder_nr label"
|
||||
#~ msgid "Inner Walls Extruder"
|
||||
#~ msgstr "Extrusor de paredes interiores"
|
||||
|
||||
#~ msgctxt "wall_extruder_nr description"
|
||||
#~ msgid "The extruder train used for printing the walls. This is used in multi-extrusion."
|
||||
#~ msgstr "El tren extrusor que se utiliza para imprimir paredes. Se emplea en la extrusión múltiple."
|
||||
#~ msgctxt "infill_pattern description"
|
||||
#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, octet, quarter cubic and concentric patterns are fully printed every layer. Cubic, quarter cubic and octet infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
#~ msgstr "Patrón del material de relleno de la impresión. El relleno de línea y zigzag cambia de dirección en capas alternas, con lo que se reduce el coste del material. Los patrones de rejilla, triángulo, cúbico, de octeto, cúbico bitruncado y concéntrico se imprimen en todas las capas por completo. El relleno cúbico, cúbico bitruncado y de octeto cambian en cada capa para proporcionar una distribución de fuerza equitativa en cada dirección."
|
||||
|
||||
#~ msgctxt "zig_zaggify_infill description"
|
||||
#~ msgid "Connect the ends where the infill pattern meets the inner wall using a lines which follows the shape of the inner wall. Enabling this setting can make the infill adhere to the walls better and reduces the effects on infill on the quality of vertical surfaces. Disabling this setting reduces the amount of material used."
|
||||
#~ msgstr "Conectar los extremos donde los patrones de relleno se juntan con la pared interior usando una línea que siga la forma de esta. Habilitar este ajuste puede lograr que el relleno se adhiera mejor a las paredes y reduce el efecto del relleno sobre la calidad de las superficies verticales. Deshabilitar este ajuste reduce la cantidad de material utilizado."
|
||||
|
||||
#~ msgctxt "skirt_gap description"
|
||||
#~ msgid ""
|
||||
#~ "The horizontal distance between the skirt and the first layer of the print.\n"
|
||||
#~ "This is the minimum distance, multiple skirt lines will extend outwards from this distance."
|
||||
#~ msgstr ""
|
||||
#~ "La distancia horizontal entre la falda y la primera capa de la impresión.\n"
|
||||
#~ "Esta es la distancia mínima; múltiples líneas de falda se extenderán hacia el exterior a partir de esta distancia."
|
||||
|
||||
#~ msgctxt "z_offset_layer_0 label"
|
||||
#~ msgid "Initial Layer Z Offset"
|
||||
#~ msgstr "Desplazamiento Z de la capa inicial"
|
||||
|
||||
#~ msgctxt "z_offset_layer_0 description"
|
||||
#~ msgid "The extruder is offset from the normal height of the first layer by this amount. It can be positive (raised) or negative (lowered). Some filament types adhere to the build plate better if the extruder is raised slightly."
|
||||
#~ msgstr "El extrusor se desplaza de la altura normal de la primera capa con este valor, el cual puede ser positivo (elevado) o negativo (bajo). Algunas clases de filamentos se adhieren mejor a la placa de impresión si se levanta ligeramente el extrusor."
|
||||
|
||||
#~ msgctxt "z_offset_taper_layers label"
|
||||
#~ msgid "Z Offset Taper Layers"
|
||||
#~ msgstr "Desplazamiento Z de capas en disminución"
|
||||
|
||||
#~ msgctxt "z_offset_taper_layers description"
|
||||
#~ msgid "When non-zero, the Z offset is reduced to 0 over that many layers. A value of 0 means that the Z offset remains constant for all the layers in the print."
|
||||
#~ msgstr "Si no es cero, el desplazamiento Z se reduce a cero en las capas. Un valor de cero implica que el desplazamiento Z se mantiene constante en todas las capas de impresión."
|
||||
|
||||
#~ msgctxt "raft_smoothing description"
|
||||
#~ msgid "This setting control how much inner corners in the raft outline are rounded. Inward corners are rounded to a semi circle with a radius equal to the value given here. This setting also removes holes in the raft outline which are smaller than such a circle."
|
||||
#~ msgstr "Este ajuste controla la medida en que se redondean las esquinas interiores en el contorno de la balsa. Las esquinas hacia el interior se redondean en semicírculo con un radio equivalente al valor aquí indicado. Este ajuste también elimina los orificios del contorno de la balsa que sean más pequeños que dicho círculo."
|
||||
|
||||
#~ msgctxt "infill_pattern description"
|
||||
#~ msgid "The pattern of the infill material of the print. The line and zig zag infill swap direction on alternate layers, reducing material cost. The grid, triangle, cubic, tetrahedral and concentric patterns are fully printed every layer. Cubic and tetrahedral infill change with every layer to provide a more equal distribution of strength over each direction."
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user