Merge pull request #2817 from Ultimaker/print_simulation_view

Print simulation view
This commit is contained in:
alekseisasin 2017-11-20 14:34:52 +01:00 committed by GitHub
commit db88d5b54c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 3348 additions and 2159 deletions

1
.gitignore vendored
View file

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

View file

@ -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()

View file

@ -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:
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
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 -= 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:
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
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 -= 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"):

View file

@ -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):

View file

@ -200,6 +200,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 +218,7 @@ class CuraApplication(QtApplication):
"CuraEngineBackend",
"UserAgreement",
"SolidView",
"LayerView",
"SimulationView",
"STLReader",
"SelectionTool",
"CameraTool",
@ -260,14 +261,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 +279,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 +430,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 +438,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 +704,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))
@ -733,6 +735,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 +790,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 +799,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 +1386,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)

View file

@ -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

View file

@ -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"
}
}

View file

@ -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
@ -166,6 +172,14 @@ class LayerPolygon:
@property
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):

View file

@ -17,7 +17,7 @@ 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 . import ExtruderStack
@ -42,12 +42,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 +257,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 +301,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 +365,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 +399,65 @@ class CuraContainerRegistry(ContainerRegistry):
return new_stack
def _registerSingleExtrusionMachinesExtruderStacks(self):
machines = ContainerRegistry.getInstance().findContainerStacks(machine_extruder_trains = {"0": "fdmextruder"})
for machine in machines:
self.addExtruderStackForSingleExtrusionMachine(machine, "fdmextruder")
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id):
new_extruder_id = extruder_id
extruder_stack = None
# if extruders are defined in the machine definition use those instead
if machine.extruders and len(machine.extruders) > 0:
new_extruder_id = machine.extruders["0"].getId()
extruder_stack = machine.extruders["0"]
# if the extruder stack doesn't exist yet we create and add it
if not extruder_stack:
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)
if machine.userChanges:
# set existing user changes if found
extruder_stack.setUserChanges(machine.userChanges)
else:
# 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(extruder_definition)
extruder_stack.setUserChanges(user_container)
self.addContainer(user_container)
variant_id = "default"
if machine.variant.getId() != "empty_variant":
variant_id = machine.variant.getId()
extruder_stack.setVariantById(variant_id)
extruder_stack.setMaterialById("default")
extruder_stack.setQualityById("default")
self.addContainer(extruder_stack)
# 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():

View file

@ -396,7 +396,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

View file

@ -47,21 +47,40 @@ class CuraStackBuilder:
new_global_stack.setName(generated_name)
for extruder_definition in 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.id)
extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId())
new_extruder_id = registry.uniqueName(extruder_definition.id)
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
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:
Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id)
new_extruder_id = registry.uniqueName(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)
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")

View file

@ -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
@ -59,10 +58,10 @@ class ExtruderManager(QObject):
@pyqtProperty(str, notify = activeExtruderChanged)
def activeExtruderStackId(self) -> Optional[str]:
if not Application.getInstance().getGlobalContainerStack():
return None # No active machine, so no active extruder.
return None # No active machine, so no active extruder.
try:
return self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId()
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
return None
## Return extruder count according to extruder trains.
@ -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

View file

@ -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",

View file

@ -71,13 +71,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self._add_global = False
self._simple_names = False
self._active_machine_extruders = [] # type: Iterable[ExtruderStack]
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()

View file

@ -23,9 +23,9 @@ class GlobalStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
self.addMetaDataEntry("type", "machine") # For backward compatibility
self.addMetaDataEntry("type", "machine") # For backward compatibility
self._extruders = {}
self._extruders = {} # type: Dict[str, "ExtruderStack"]
# This property is used to track which settings we are calculating the "resolve" for
# 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)

View file

@ -107,9 +107,8 @@ 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)
# Make sure _active_container_stack is properly initiated
ExtruderManager.getInstance().setActiveExtruderIndex(0)
self._auto_materials_changed = {}
self._auto_hotends_changed = {}
@ -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,52 +273,38 @@ 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)
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.
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 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)
global_material = self._global_container_stack.material
if global_material != self._empty_material_container:
self._global_container_stack.setMaterial(self._empty_material_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.
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()
# 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)
self._error_check_timer.start()
@ -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
@ -732,15 +706,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.
@ -947,35 +919,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.
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True")
result.append({
"stack": extruder_stack,
"quality": quality,
"quality_changes": empty_quality_changes
})
if not global_quality:
global_quality = self._empty_quality_container
# also find a global quality for the machine
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [], global_quality = "True")
result.append({"stack": global_container_stack, "quality": global_quality, "quality_changes": empty_quality_changes})
# 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
})
return result
@ -988,10 +971,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 +983,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 +1006,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:
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True")
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})
# 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
})
return result
@ -1156,10 +1147,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 +1175,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 +1190,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 +1198,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 +1211,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")

View file

@ -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,18 +58,12 @@ 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
materials = [extruder.material for extruder in extruder_stacks]
extruder_stacks = self._getOrderedExtruderStacksList()
materials = [extruder.material for extruder in extruder_stacks]
# Fetch the list of usable qualities across all extruders.
# The actual list of quality profiles come from the first extruder in the extruder list.
@ -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)

View file

@ -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
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]
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())]
return quality_list + filtered_quality_changes

View file

@ -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)

View file

@ -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)

View file

@ -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._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
self._stack.propertyChanged.connect(self._onSettingChanged)

View file

@ -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
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]
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())]
return filtered_quality_changes

View file

@ -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
default_stack = ExtruderManager.getInstance().getExtruderStack(0)
# 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)
if default_stack:
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()

View file

@ -644,9 +644,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]
@ -682,12 +683,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 +697,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:
@ -749,9 +739,15 @@ 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:
extruder_stacks.append(stack)
stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
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:
self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder")
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.
@ -784,7 +780,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
for stack in [global_stack] + extruder_stacks:
stack.replaceContainer(_ContainerIndexes.Quality, empty_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.

View file

@ -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")

View file

@ -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
}

View file

@ -608,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.

View file

@ -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:
@ -109,6 +109,7 @@ class ProcessSlicedLayersJob(Job):
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 +128,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 +147,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 +221,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 +234,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:

View file

@ -159,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:
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack)
else:
self._buildExtruderMessageFromGlobalStack(stack)
# Build messages for extruder stacks
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack)
for group in object_groups:
group_message = self._slice_message.addRepeatedMessage("object_lists")
@ -251,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

View file

@ -40,7 +40,8 @@ class GCodeReader(MeshReader):
self._extruder_number = 0
self._clearValues()
self._scene_node = None
self._position = namedtuple('Position', ['x', 'y', 'z', 'e'])
# X, Y, Z position, F feedrate and E extruder values are stored
self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e'])
self._is_layers_in_file = False # Does the Gcode have the layers comment?
self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
self._current_layer_thickness = 0.2 # default
@ -48,12 +49,15 @@ class GCodeReader(MeshReader):
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)
@staticmethod
def _getValue(line, code):
@ -96,7 +100,7 @@ class GCodeReader(MeshReader):
def _createPolygon(self, layer_thickness, path, extruder_offsets):
countvalid = 0
for point in path:
if point[3] > 0:
if point[5] > 0:
countvalid += 1
if countvalid >= 2:
# we know what to do now, no need to count further
@ -114,46 +118,83 @@ class GCodeReader(MeshReader):
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_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_types[i - 1] = point[3]
if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
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)
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 _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, 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
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:
if params.e > e[self._extruder_number]:
path.append([x, y, z, self._layer_type]) # extrusion
new_extrusion_value = params.e if self._is_absolute_positioning else e[self._extruder_number] + params.e
if new_extrusion_value > e[self._extruder_number]:
path.append([x, y, z, f, params.e + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
else:
path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction
e[self._extruder_number] = params.e
path.append([x, y, z, f, params.e + 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 + 0.05 # allow a tiny overlap
self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
self._previous_z = z
else:
path.append([x, y, z, LayerPolygon.MoveCombingType])
return self._position(x, y, z, e)
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
@ -164,17 +205,31 @@ class GCodeReader(MeshReader):
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
0,
position.f,
position.e)
## Set the absolute positioning
def _gCode90(self, position, params, path):
self._is_absolute_positioning = True
return position
## Set the relative positioning
def _gCode91(self, position, params, path):
self._is_absolute_positioning = False
return position
## Reset the current position to the values specified.
# 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):
@ -182,7 +237,7 @@ class GCodeReader(MeshReader):
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
x, y, z, f, e = None, None, None, None, None
for item in s[1:]:
if len(item) <= 1:
continue
@ -194,17 +249,20 @@ class GCodeReader(MeshReader):
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 (x is not None and x < 0) or (y is not None and y < 0):
if self._is_absolute_positioning and ((x is not None and x < 0) or (y is not None and y < 0)):
self._center_is_zero = True
params = self._position(x, y, z, e)
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
@ -223,6 +281,8 @@ class GCodeReader(MeshReader):
def read(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
@ -260,7 +320,7 @@ class GCodeReader(MeshReader):
Logger.log("d", "Parsing %s..." % file_name)
current_position = self._position(0, 0, 0, [0])
current_position = self._position(0, 0, 0, 0, [0])
current_path = []
for line in file:
@ -293,6 +353,7 @@ class GCodeReader(MeshReader):
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):])
@ -308,17 +369,12 @@ class GCodeReader(MeshReader):
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)
# < 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
# 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:
@ -327,8 +383,8 @@ class GCodeReader(MeshReader):
current_position = self._processTCode(T, line, current_position, current_path)
# "Flush" leftovers
if not self._is_layers_in_file and len(current_path) > 1:
# "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()

View file

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

View file

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

View file

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

View file

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

View file

@ -116,8 +116,7 @@ class MachineSettingsAction(MachineAction):
@pyqtSlot(int)
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,52 +148,29 @@ 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
extruder_manager.setActiveExtruderIndex(0)
# 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()
# Move settable_per_extruder values out of the global container
if previous_extruder_count == 1:
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
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()
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
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")
# 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)
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)
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 = ExtruderManager.getInstance().getActiveExtruderStack().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]
stacks = ExtruderManager.getInstance().getExtruderStacks()
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:

View file

@ -78,31 +78,26 @@ 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)
root_node = Application.getInstance().getController().getScene().getRoot()
for node in DepthFirstIterator(root_node):
new_stack_id = default_stack_id
# Get position of old extruder stack for this node
old_extruder_pos = node.callDecoration("getActiveExtruderPosition")
if old_extruder_pos is not None:
# Fetch current (new) extruder stack at position
new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos)
if new_stack:
new_stack_id = new_stack.getId()
node.callDecoration("setActiveExtruder", new_stack_id)
if extruder_stack:
root_node = Application.getInstance().getController().getScene().getRoot()
for node in DepthFirstIterator(root_node):
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:
# Fetch current (new) extruder stack at position
new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos)
if new_stack:
new_stack_id = new_stack.getId()
node.callDecoration("setActiveExtruder", new_stack_id)
self._updateEnabled()
self._updateEnabled()
def _updateEnabled(self):
selected_objects = Selection.getAllSelectedObjects()

View file

@ -1,312 +1,325 @@
// 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 real minimumRangeHandleSize: handleSize / 2
property color upperHandleColor: "black"
property color lowerHandleColor: "black"
property color rangeHandleColor: "black"
property real handleLabelWidth: width
property var activeHandle: upperHandle
// 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 real minimumValue: 0
property real minimumRange: 0 // minimum range allowed between min and max values
property bool roundValues: true
property real upperValue: maximumValue
property real lowerValue: minimumValue
property bool layersVisible: true
function getUpperValueFromSliderHandle () {
return upperHandle.getValue()
}
function setUpperValue (value) {
upperHandle.setValue(value)
updateRangeHandle()
}
function getLowerValueFromSliderHandle () {
return lowerHandle.getValue()
}
function setLowerValue (value) {
lowerHandle.setValue(value)
updateRangeHandle()
}
function updateRangeHandle () {
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
}
// set the active handle to show only one label at a time
function setActiveHandle (handle) {
activeHandle = handle
}
// slider track
Rectangle {
id: track
width: sliderRoot.trackThickness
height: sliderRoot.height - sliderRoot.handleSize
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
border.width: sliderRoot.trackBorderWidth
border.color: sliderRoot.trackBorderColor
visible: sliderRoot.layersVisible
}
// Range handle
Item {
id: rangeHandle
y: upperHandle.y + upperHandle.height
width: sliderRoot.handleSize
height: sliderRoot.minimumRangeHandleSize
anchors.horizontalCenter: sliderRoot.horizontalCenter
visible: sliderRoot.layersVisible
// set the new value when dragging
function onHandleDragged () {
upperHandle.y = y - upperHandle.height
lowerHandle.y = y + height
var upperValue = sliderRoot.getUpperValueFromSliderHandle()
var lowerValue = sliderRoot.getLowerValueFromSliderHandle()
// set both values after moving the handle position
UM.LayerView.setCurrentLayer(upperValue)
UM.LayerView.setMinimumLayer(lowerValue)
}
function setValue (value) {
var range = sliderRoot.upperValue - sliderRoot.lowerValue
value = Math.min(value, sliderRoot.maximumValue)
value = Math.max(value, sliderRoot.minimumValue + range)
UM.LayerView.setCurrentLayer(value)
UM.LayerView.setMinimumLayer(value - range)
}
Rectangle {
width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
height: parent.height + sliderRoot.handleSize
anchors.centerIn: parent
color: sliderRoot.rangeHandleColor
}
MouseArea {
anchors.fill: parent
drag {
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height
maximumY: sliderRoot.height - (rangeHandle.height + lowerHandle.height)
}
onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(rangeHandle)
}
LayerSliderLabel {
id: rangleHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.LayerView.busy
setValue: rangeHandle.setValue // connect callback functions
}
}
// Upper handle
Rectangle {
id: upperHandle
y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
width: sliderRoot.handleSize
height: sliderRoot.handleSize
anchors.horizontalCenter: sliderRoot.horizontalCenter
radius: sliderRoot.handleRadius
color: sliderRoot.upperHandleColor
visible: sliderRoot.layersVisible
function onHandleDragged () {
// don't allow the lower handle to be heigher than the upper handle
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
}
// update the range handle
sliderRoot.updateRangeHandle()
// set the new value after moving the handle position
UM.LayerView.setCurrentLayer(getValue())
}
// get the upper value based on the slider position
function getValue () {
var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
// set the slider position based on the upper value
function setValue (value) {
UM.LayerView.setCurrentLayer(value)
var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
y = newUpperYPosition
// update the range handle
sliderRoot.updateRangeHandle()
}
// dragging
MouseArea {
anchors.fill: parent
drag {
target: parent
axis: Drag.YAxis
minimumY: 0
maximumY: sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)
}
onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(upperHandle)
}
LayerSliderLabel {
id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.LayerView.busy
setValue: upperHandle.setValue // connect callback functions
}
}
// Lower handle
Rectangle {
id: lowerHandle
y: sliderRoot.height - sliderRoot.handleSize
width: parent.handleSize
height: parent.handleSize
anchors.horizontalCenter: parent.horizontalCenter
radius: sliderRoot.handleRadius
color: sliderRoot.lowerHandleColor
visible: slider.layersVisible
function onHandleDragged () {
// don't allow the upper handle to be lower than the lower handle
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
}
// update the range handle
sliderRoot.updateRangeHandle()
// set the new value after moving the handle position
UM.LayerView.setMinimumLayer(getValue())
}
// get the lower value from the current slider position
function getValue () {
var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
// set the slider position based on the lower value
function setValue (value) {
UM.LayerView.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)))
y = newLowerYPosition
// update the range handle
sliderRoot.updateRangeHandle()
}
// dragging
MouseArea {
anchors.fill: parent
drag {
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
maximumY: sliderRoot.height - parent.height
}
onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(lowerHandle)
}
LayerSliderLabel {
id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.lowerValue
busy: UM.LayerView.busy
setValue: lowerHandle.setValue // connect callback functions
}
}
}
// 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 real minimumRangeHandleSize: handleSize / 2
property color upperHandleColor: "black"
property color lowerHandleColor: "black"
property color rangeHandleColor: "black"
property color handleActiveColor: "white"
property real handleLabelWidth: width
property var activeHandle: upperHandle
// 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 real minimumValue: 0
property real minimumRange: 0 // minimum range allowed between min and max values
property bool roundValues: true
property real upperValue: maximumValue
property real lowerValue: minimumValue
property bool layersVisible: true
function getUpperValueFromSliderHandle () {
return upperHandle.getValue()
}
function setUpperValue (value) {
upperHandle.setValue(value)
updateRangeHandle()
}
function getLowerValueFromSliderHandle () {
return lowerHandle.getValue()
}
function setLowerValue (value) {
lowerHandle.setValue(value)
updateRangeHandle()
}
function updateRangeHandle () {
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
}
// set the active handle to show only one label at a time
function setActiveHandle (handle) {
activeHandle = handle
}
// slider track
Rectangle {
id: track
width: sliderRoot.trackThickness
height: sliderRoot.height - sliderRoot.handleSize
radius: sliderRoot.trackRadius
anchors.centerIn: sliderRoot
color: sliderRoot.trackColor
border.width: sliderRoot.trackBorderWidth
border.color: sliderRoot.trackBorderColor
visible: sliderRoot.layersVisible
}
// Range handle
Item {
id: rangeHandle
y: upperHandle.y + upperHandle.height
width: sliderRoot.handleSize
height: sliderRoot.minimumRangeHandleSize
anchors.horizontalCenter: sliderRoot.horizontalCenter
visible: sliderRoot.layersVisible
// set the new value when dragging
function onHandleDragged () {
upperHandle.y = y - upperHandle.height
lowerHandle.y = y + height
var upperValue = sliderRoot.getUpperValueFromSliderHandle()
var lowerValue = sliderRoot.getLowerValueFromSliderHandle()
// set both values after moving the handle position
UM.SimulationView.setCurrentLayer(upperValue)
UM.SimulationView.setMinimumLayer(lowerValue)
}
function setValue (value) {
var range = sliderRoot.upperValue - sliderRoot.lowerValue
value = Math.min(value, sliderRoot.maximumValue)
value = Math.max(value, sliderRoot.minimumValue + range)
UM.SimulationView.setCurrentLayer(value)
UM.SimulationView.setMinimumLayer(value - range)
}
Rectangle {
width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
height: parent.height + sliderRoot.handleSize
anchors.centerIn: parent
color: sliderRoot.rangeHandleColor
}
MouseArea {
anchors.fill: parent
drag {
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height
maximumY: sliderRoot.height - (rangeHandle.height + lowerHandle.height)
}
onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(rangeHandle)
}
SimulationSliderLabel {
id: rangleHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.SimulationView.busy
setValue: rangeHandle.setValue // connect callback functions
}
}
// Upper handle
Rectangle {
id: upperHandle
y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
width: sliderRoot.handleSize
height: sliderRoot.handleSize
anchors.horizontalCenter: sliderRoot.horizontalCenter
radius: sliderRoot.handleRadius
color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
visible: sliderRoot.layersVisible
function onHandleDragged () {
// don't allow the lower handle to be heigher than the upper handle
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
}
// update the range handle
sliderRoot.updateRangeHandle()
// set the new value after moving the handle position
UM.SimulationView.setCurrentLayer(getValue())
}
// get the upper value based on the slider position
function getValue () {
var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
// set the slider position based on the upper value
function setValue (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)))
y = newUpperYPosition
// update the range handle
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
drag {
target: parent
axis: Drag.YAxis
minimumY: 0
maximumY: sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)
}
onPositionChanged: parent.onHandleDragged()
onPressed: {
sliderRoot.setActiveHandle(upperHandle)
upperHandleLabel.forceActiveFocus()
}
}
SimulationSliderLabel {
id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.upperValue
busy: UM.SimulationView.busy
setValue: upperHandle.setValue // connect callback functions
}
}
// Lower handle
Rectangle {
id: lowerHandle
y: sliderRoot.height - sliderRoot.handleSize
width: parent.handleSize
height: parent.handleSize
anchors.horizontalCenter: parent.horizontalCenter
radius: sliderRoot.handleRadius
color: lowerHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.lowerHandleColor
visible: sliderRoot.layersVisible
function onHandleDragged () {
// don't allow the upper handle to be lower than the lower handle
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
}
// update the range handle
sliderRoot.updateRangeHandle()
// set the new value after moving the handle position
UM.SimulationView.setMinimumLayer(getValue())
}
// get the lower value from the current slider position
function getValue () {
var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
result = sliderRoot.roundValues ? Math.round(result) : result
return result
}
// set the slider position based on the lower value
function setValue (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)))
y = newLowerYPosition
// update the range handle
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
drag {
target: parent
axis: Drag.YAxis
minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
maximumY: sliderRoot.height - parent.height
}
onPositionChanged: parent.onHandleDragged()
onPressed: {
sliderRoot.setActiveHandle(lowerHandle)
lowerHandleLabel.forceActiveFocus()
}
}
SimulationSliderLabel {
id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - width - UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
// custom properties
maximumValue: sliderRoot.maximumValue
value: sliderRoot.lowerValue
busy: UM.SimulationView.busy
setValue: lowerHandle.setValue // connect callback functions
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,187 @@
# 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 plugins.SimulationView.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._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.Solid)
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
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num:
self._current_shader = self._layer_shader
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 once we know the correct position
if 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.Solid)
nozzle_batch.addItem(nozzle_node.getWorldTransformation(), mesh = nozzle_node.getMeshData())
nozzle_batch.render(self._scene.getActiveCamera())
# Render toolhandles on top of the layerview
if len(tool_handle_batch.items) > 0:
tool_handle_batch.render(self._scene.getActiveCamera())
self.release()

View file

@ -1,103 +1,104 @@
// 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
UM.PointingRectangle {
id: sliderLabelRoot
// custom properties
property real maximumValue: 100
property real value: 0
property var setValue // Function
property bool busy: false
target: Qt.point(parent.width, y + height / 2)
arrowSize: UM.Theme.getSize("default_arrow").width
height: parent.height
width: valueLabel.width + UM.Theme.getSize("default_margin").width
visible: false
// make sure the text field is focussed when pressing the parent handle
// needed to connect the key bindings when switching active handle
onVisibleChanged: if (visible) valueLabel.forceActiveFocus()
color: UM.Theme.getColor("tool_panel_background")
borderColor: UM.Theme.getColor("lining")
borderWidth: UM.Theme.getSize("default_lining").width
Behavior on height {
NumberAnimation {
duration: 50
}
}
// catch all mouse events so they're not handled by underlying 3D scene
MouseArea {
anchors.fill: parent
}
TextField {
id: valueLabel
anchors {
left: parent.left
leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter
}
width: 40 * screenScaleFactor
text: sliderLabelRoot.value + 1 // 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)
Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
style: TextFieldStyle {
textColor: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
background: Item { }
}
onEditingFinished: {
// Ensure that the cursor is at the first position. On some systems the text isn't fully visible
// Seems to have to do something with different dpi densities that QML doesn't quite handle.
// Another option would be to increase the size even further, but that gives pretty ugly results.
cursorPosition = 0
if (valueLabel.text != "") {
// -1 because we need to convert back to an array structure
sliderLabelRoot.setValue(parseInt(valueLabel.text) - 1)
}
}
validator: IntValidator {
bottom: 1
top: sliderLabelRoot.maximumValue + 1 // +1 because actual layers is an array
}
}
BusyIndicator {
id: busyIndicator
anchors {
left: parent.right
leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter
}
width: sliderLabelRoot.height
height: width
visible: sliderLabelRoot.busy
running: sliderLabelRoot.busy
}
}
// 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
UM.PointingRectangle {
id: sliderLabelRoot
// custom properties
property real maximumValue: 100
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
height: parent.height
width: valueLabel.width + UM.Theme.getSize("default_margin").width
visible: false
// make sure the text field is focussed when pressing the parent handle
// needed to connect the key bindings when switching active handle
onVisibleChanged: if (visible) valueLabel.forceActiveFocus()
color: UM.Theme.getColor("tool_panel_background")
borderColor: UM.Theme.getColor("lining")
borderWidth: UM.Theme.getSize("default_lining").width
Behavior on height {
NumberAnimation {
duration: 50
}
}
// catch all mouse events so they're not handled by underlying 3D scene
MouseArea {
anchors.fill: parent
}
TextField {
id: valueLabel
anchors {
left: parent.left
leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter
}
width: 40 * screenScaleFactor
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)
Keys.onUpPressed: sliderLabelRoot.setValue(sliderLabelRoot.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
Keys.onDownPressed: sliderLabelRoot.setValue(sliderLabelRoot.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
style: TextFieldStyle {
textColor: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
background: Item { }
}
onEditingFinished: {
// Ensure that the cursor is at the first position. On some systems the text isn't fully visible
// Seems to have to do something with different dpi densities that QML doesn't quite handle.
// Another option would be to increase the size even further, but that gives pretty ugly results.
cursorPosition = 0
if (valueLabel.text != "") {
// -startFrom because we need to convert back to an array structure
sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom)
}
}
validator: IntValidator {
bottom:startFrom
top: sliderLabelRoot.maximumValue + startFrom // +startFrom because maybe we want to start in a different value rather than 0
}
}
BusyIndicator {
id: busyIndicator
anchors {
left: parent.right
leftMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
verticalCenter: parent.verticalCenter
}
width: sliderLabelRoot.height
height: width
visible: sliderLabelRoot.busy
running: sliderLabelRoot.busy
}
}

View file

@ -1,46 +1,46 @@
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
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.Math.Vector import Vector
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.SceneNode import SceneNode
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 plugins.SimulationView.NozzleNode import NozzleNode
from . import SimulationPass, SimulationViewProxy
from . import LayerPass
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.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.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,26 @@ 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()
def isBusy(self):
return self._busy
@ -136,9 +163,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 +223,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 +308,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 +331,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 +364,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 +413,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 +445,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 +475,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 +511,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:

View file

@ -0,0 +1,645 @@
// 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 (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)
}
}
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: layerSlider.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.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 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.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
anchors.rightMargin: 10 * screenScaleFactor
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
view_settings.show_legend = UM.SimulationView.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.SimulationView.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.SimulationView.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.SimulationView.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 & !view_settings.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: view_settings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Helpers"),
initialValue: view_settings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegendModel.append({
label: catalog.i18nc("@label", "Show Shell"),
initialValue: view_settings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegendModel.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.SimulationView.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.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: 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")
}
}
// Text for the minimum, maximum and units for the feedrates and layer thickness
Rectangle {
id: gradientLegend
visible: view_settings.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 and thickness
Rectangle { // In QML 5.9 can be changed by LinearGradient
// Invert values because then the bar is rotated 90 degrees
id: gradient
visible: view_settings.show_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.75, 0.5, 0.25, 1)
}
GradientStop {
position: 0.5
color: Qt.rgba(0.5, 1, 0.5, 1)
}
GradientStop {
position: 0.75
color: Qt.rgba(0.25, 0.5, 0.75, 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
width: parent.width
height: UM.Theme.getSize("slider_handle").width
anchors.left: parent.left
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: pathSlider.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
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
implicitWidth: UM.Theme.getSize("button").width * 0.75;
implicitHeight: UM.Theme.getSize("button").height * 0.75;
iconSource: "./resources/simulation_resume.svg"
style: UM.Theme.styles.tool_button
visible: !UM.SimulationView.compatibilityMode
anchors {
horizontalCenter: layerSlider.horizontalCenter
top: layerSlider.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").width
}
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: 250
running: false
repeat: true
onTriggered: {
var currentPath = UM.SimulationView.currentPath
var numPaths = UM.SimulationView.numPaths
var currentLayer = UM.SimulationView.currentLayer
var numLayers = UM.SimulationView.numLayers
// When the user plays the simulation, if the path slider is at the end of this layer, we start
// the simulation at the beginning of the current layer.
if (playButton.status == 0)
{
if (currentPath >= numPaths)
{
UM.SimulationView.setCurrentPath(0)
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
// If the simulation is already playing and we reach the end of a layer, then it automatically
// starts at the beginning of the next layer.
else
{
if (currentPath >= numPaths)
{
// At the end of the model, the simulation stops
if (currentLayer >= numLayers)
{
playButton.pauseSimulation()
}
else
{
UM.SimulationView.setCurrentLayer(currentLayer+1)
UM.SimulationView.setCurrentPath(0)
}
}
else
{
UM.SimulationView.setCurrentPath(currentPath+1)
}
}
playButton.status = 1
}
}
}
FontMetrics {
id: fontMetrics
font: UM.Theme.getFont("default")
}
}

View file

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

View file

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

View file

@ -6,6 +6,10 @@ vertex41core =
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_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,15 @@ vertex41core =
out highp vec3 f_vertex;
out highp vec3 f_normal;
vec4 gradientColor(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-2*value);
float blue = 1-value;
return vec4(red, green, blue, 1.0);
}
void main()
{
vec4 v1_vertex = a_vertex;
@ -48,6 +63,12 @@ vertex41core =
case 1: // "Line type"
v_color = a_color;
break;
case 2: // "Feedrate"
v_color = gradientColor(a_feedrate, u_min_feedrate, u_max_feedrate);
break;
case 3: // "Layer thickness"
v_color = gradientColor(a_line_dim.y, u_min_thickness, u_max_thickness);
break;
}
v_vertex = world_space_vert.xyz;
@ -247,6 +268,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 +289,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

View file

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

View file

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

View file

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

Binary file not shown.

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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"
viewBox="0 0 30 30"
version="1.1"
id="svg4620"
sodipodi:docname="simulation_pause.svg"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)">
<metadata
id="metadata4626">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4624" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1137"
id="namedview4622"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="22.250293"
inkscape:cx="8.1879003"
inkscape:cy="12.643765"
inkscape:window-x="2872"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4620">
<sodipodi:guide
position="15,15"
orientation="1,0"
id="guide4628"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="15,15"
orientation="0,1"
id="guide4630"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
</sodipodi:namedview>
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5192"
width="2"
height="20"
x="19"
y="5" />
<rect
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect5192-5"
width="2"
height="20"
x="9"
y="5" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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"
viewBox="0 0 30 30"
version="1.1"
id="svg3765"
sodipodi:docname="simulation_resume.svg"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)">
<metadata
id="metadata3771">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3769" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1137"
id="namedview3767"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="23.327047"
inkscape:cx="10.788646"
inkscape:cy="14.67951"
inkscape:window-x="2872"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg3765">
<sodipodi:guide
position="15,15"
orientation="0,1"
id="guide4592"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="15,15"
orientation="1,0"
id="guide4594"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
</sodipodi:namedview>
<path
sodipodi:type="star"
id="path3783"
sodipodi:sides="3"
sodipodi:cx="12.732001"
sodipodi:cy="14.695877"
sodipodi:r1="13.891838"
sodipodi:r2="6.945919"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 26.623839,14.695877 -20.8377567,12.030685 0,-24.0613696 z"
inkscape:transform-center-x="-2.9211205"
style="fill:none;stroke:#000000;stroke-width:2.32790732;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
transform="matrix(0.84110413,0,0,0.87756418,1.775541,2.1034247)" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -87,15 +87,10 @@ 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]
extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position"))
for extruder in extruders:
extruder_dict = dict()

View file

@ -46,19 +46,10 @@ 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_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr)
if Preferences.getInstance().getValue("view/show_overhang"):
angle = support_angle_stack.getProperty("support_angle", "value")
@ -71,33 +62,26 @@ 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")
if extruder_id:
extruder_index = max(0, self._extruders_model.find("id", extruder_id))
try:
material_color = self._extruders_model.getItem(extruder_index)["color"]
except KeyError:
material_color = self._extruders_model.defaultColors[0]
# Get color to render this mesh in from ExtrudersModel
extruder_index = 0
extruder_id = node.callDecoration("getActiveExtruder")
if extruder_id:
extruder_index = max(0, self._extruders_model.find("id", extruder_id))
try:
material_color = self._extruders_model.getItem(extruder_index)["color"]
except KeyError:
material_color = self._extruders_model.defaultColors[0]
if extruder_index != ExtruderManager.getInstance().activeExtruderIndex:
# Shade objects that are printed with the non-active extruder 25% darker
shade_factor = 0.6
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])

View file

@ -37,7 +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_container = global_container_stack.extruders["0"].variant
variant_index = global_container_stack.getContainerIndex(variant_container)
if has_variants:
@ -52,7 +52,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.

View file

@ -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]

View file

@ -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

View file

@ -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 },

View file

@ -8,7 +8,8 @@
"author": "Ultimaker",
"manufacturer": "Unknown",
"setting_version": 1,
"visible": false
"visible": false,
"position": "0"
},
"settings":
{

View file

@ -1340,6 +1340,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
}
}
},
@ -3495,7 +3613,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
@ -5119,7 +5237,7 @@
"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",
@ -5825,124 +5943,6 @@
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
"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
}
}
},

View file

@ -177,7 +177,7 @@ UM.MainWindow
MenuSeparator { }
MenuItem { text: catalog.i18nc("@action:inmenu", "Set as Active Extruder"); onTriggered: ExtruderManager.setActiveExtruderIndex(model.index) }
MenuItem { text: catalog.i18nc("@action:inmenu", "Set as Active Extruder"); onTriggered: Cura.ExtruderManager.setActiveExtruderIndex(model.index) }
}
onObjectAdded: settingsMenu.insertItem(index, object)
onObjectRemoved: settingsMenu.removeItem(object)

View file

@ -18,7 +18,7 @@ Button
style: UM.Theme.styles.tool_button;
iconSource: UM.Theme.getIcon("extruder_button")
checked: ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1
checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1
enabled: UM.Selection.hasSelection
property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button");

View file

@ -31,7 +31,7 @@ Menu
visible: base.shouldShowExtruders
enabled: UM.Selection.hasSelection
checkable: true
checked: ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1
checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1
onTriggered: CuraActions.setExtruderForSelection(model.id)
shortcut: "Ctrl+" + (model.index + 1)
}

View file

@ -78,16 +78,16 @@ Menu
{
text: model.name
checkable: true
checked: model.id == Cura.MachineManager.allActiveMaterialIds[ExtruderManager.extruderIds[extruderIndex]]
checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]]
exclusiveGroup: group
onTriggered:
{
// This workaround is done because of the application menus for materials and variants for multiextrusion printers.
// The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI.
var activeExtruderIndex = ExtruderManager.activeExtruderIndex;
ExtruderManager.setActiveExtruderIndex(extruderIndex);
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
Cura.MachineManager.setActiveMaterial(model.id);
ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
}
}
onObjectAdded: menu.insertItem(index, object)
@ -121,16 +121,16 @@ Menu
{
text: model.name
checkable: true
checked: model.id == Cura.MachineManager.allActiveMaterialIds[ExtruderManager.extruderIds[extruderIndex]]
checked: model.id == Cura.MachineManager.allActiveMaterialIds[Cura.ExtruderManager.extruderIds[extruderIndex]]
exclusiveGroup: group
onTriggered:
{
// This workaround is done because of the application menus for materials and variants for multiextrusion printers.
// The extruder menu would always act on the correspoding extruder only, instead of acting on the extruder selected in the UI.
var activeExtruderIndex = ExtruderManager.activeExtruderIndex;
ExtruderManager.setActiveExtruderIndex(extruderIndex);
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
Cura.MachineManager.setActiveMaterial(model.id);
ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
}
}
onObjectAdded: brandMaterialsMenu.insertItem(index, object)

View file

@ -44,15 +44,15 @@ Menu
visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].hotendIds.length > extruderIndex && !isClusterPrinter
onTriggered:
{
var activeExtruderIndex = ExtruderManager.activeExtruderIndex;
ExtruderManager.setActiveExtruderIndex(extruderIndex);
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
var hotendId = Cura.MachineManager.printerOutputDevices[0].hotendIds[extruderIndex];
var itemIndex = nozzleInstantiator.model.find("name", hotendId);
if(itemIndex > -1)
{
Cura.MachineManager.setActiveVariant(nozzleInstantiator.model.getItem(itemIndex).id);
}
ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
}
}
@ -75,14 +75,14 @@ Menu
MenuItem {
text: model.name
checkable: true
checked: model.id == Cura.MachineManager.allActiveVariantIds[ExtruderManager.extruderIds[extruderIndex]]
checked: model.id == Cura.MachineManager.allActiveVariantIds[Cura.ExtruderManager.extruderIds[extruderIndex]]
exclusiveGroup: group
onTriggered:
{
var activeExtruderIndex = ExtruderManager.activeExtruderIndex;
ExtruderManager.setActiveExtruderIndex(extruderIndex);
var activeExtruderIndex = Cura.ExtruderManager.activeExtruderIndex;
Cura.ExtruderManager.setActiveExtruderIndex(extruderIndex);
Cura.MachineManager.setActiveVariant(model.id);
ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
Cura.ExtruderManager.setActiveExtruderIndex(activeExtruderIndex);
}
}
onObjectAdded: menu.insertItem(index, object)

View file

@ -41,7 +41,7 @@ Menu
MenuItem
{
text: model.name + " - " + model.layer_height
text: model.name
checkable: true
checked: Cura.MachineManager.activeQualityChangesId == model.id
exclusiveGroup: group

View file

@ -208,7 +208,7 @@ UM.ManagementPage
anchors.right: parent.right
anchors.bottom: parent.bottom
currentIndex: ExtruderManager.extruderCount > 0 ? ExtruderManager.activeExtruderIndex + 1 : 0
currentIndex: Cura.ExtruderManager.extruderCount > 0 ? Cura.ExtruderManager.activeExtruderIndex + 1 : 0
ProfileTab
{

View file

@ -87,7 +87,7 @@ Column
Label //Extruder name.
{
text: ExtruderManager.getExtruderName(index) != "" ? ExtruderManager.getExtruderName(index) : catalog.i18nc("@label", "Extruder")
text: Cura.ExtruderManager.getExtruderName(index) != "" ? Cura.ExtruderManager.getExtruderName(index) : catalog.i18nc("@label", "Extruder")
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
anchors.left: parent.left

View file

@ -157,7 +157,7 @@ Item {
var tooltipText = catalog.i18nc("@label", "This setting is always shared between all extruders. Changing it here will change the value for all extruders") + ".";
if ((resolve != "None") && (stackLevel != 0)) {
// We come here if a setting has a resolve and the setting is not manually edited.
tooltipText += " " + catalog.i18nc("@label", "The value is resolved from per-extruder values ") + "[" + ExtruderManager.getInstanceExtruderValues(definition.key) + "].";
tooltipText += " " + catalog.i18nc("@label", "The value is resolved from per-extruder values ") + "[" + Cura.ExtruderManager.getInstanceExtruderValues(definition.key) + "].";
}
base.showTooltip(tooltipText);
}

View file

@ -275,7 +275,7 @@ Item
Behavior on opacity { NumberAnimation { duration: 100 } }
enabled:
{
if(!ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1)
if (!Cura.ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1)
{
// disable all controls on the global tab, except categories
return model.type == "category"
@ -345,12 +345,12 @@ Item
if(inheritStackProvider.properties.limit_to_extruder != null && inheritStackProvider.properties.limit_to_extruder >= 0)
{
//We have limit_to_extruder, so pick that stack.
return ExtruderManager.extruderIds[String(inheritStackProvider.properties.limit_to_extruder)];
return Cura.ExtruderManager.extruderIds[String(inheritStackProvider.properties.limit_to_extruder)];
}
if(ExtruderManager.activeExtruderStackId)
if(Cura.ExtruderManager.activeExtruderStackId)
{
//We're on an extruder tab. Pick the current extruder.
return ExtruderManager.activeExtruderStackId;
return Cura.ExtruderManager.activeExtruderStackId;
}
//No extruder tab is selected. Pick the global stack. Shouldn't happen any more since we removed the global tab.
return activeMachineId;

View file

@ -14,7 +14,7 @@ Column
{
id: base;
property int currentExtruderIndex: ExtruderManager.activeExtruderIndex;
property int currentExtruderIndex: Cura.ExtruderManager.activeExtruderIndex;
property bool currentExtruderVisible: extrudersList.visible;
spacing: Math.floor(UM.Theme.getSize("sidebar_margin").width * 0.9)
@ -93,7 +93,7 @@ Column
onClicked:
{
forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values.
ExtruderManager.setActiveExtruderIndex(index);
Cura.ExtruderManager.setActiveExtruderIndex(index);
}
style: ButtonStyle

View file

@ -19,7 +19,7 @@ Item
property Action configureSettings;
property variant minimumPrintTime: PrintInformation.minimumPrintTime;
property variant maximumPrintTime: PrintInformation.maximumPrintTime;
property bool settingsEnabled: ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1
property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1
Component.onCompleted: PrintInformation.enabled = true
Component.onDestruction: PrintInformation.enabled = false

View file

@ -133,6 +133,7 @@
"slider_groove_fill": [245, 245, 245, 255],
"slider_handle": [255, 255, 255, 255],
"slider_handle_hover": [77, 182, 226, 255],
"slider_handle_active": [68, 192, 255, 255],
"slider_handle_border": [39, 44, 48, 255],
"slider_text_background": [255, 255, 255, 255],
@ -194,6 +195,7 @@
"layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255],
"layerview_support_interface": [64, 192, 255, 255],
"layerview_nozzle": [181, 166, 66, 120],
"material_compatibility_warning": [255, 255, 255, 255],

View file

@ -269,6 +269,7 @@ QtObject {
arrowSize: Theme.getSize("button_tooltip_arrow").width
color: Theme.getColor("button_tooltip")
opacity: control.hovered ? 1.0 : 0.0;
visible: control.text != ""
width: control.hovered ? button_tip.width + Theme.getSize("button_tooltip").width : 0
height: Theme.getSize("button_tooltip").height

View file

@ -183,6 +183,7 @@
"slider_groove_fill": [127, 127, 127, 255],
"slider_handle": [0, 0, 0, 255],
"slider_handle_hover": [77, 182, 226, 255],
"slider_handle_active": [68, 192, 255, 255],
"slider_handle_border": [39, 44, 48, 255],
"slider_text_background": [255, 255, 255, 255],
@ -271,7 +272,8 @@
"layerview_support_infill": [0, 255, 255, 255],
"layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255],
"layerview_support_interface": [64, 192, 255, 255]
"layerview_support_interface": [64, 192, 255, 255],
"layerview_nozzle": [181, 166, 66, 50]
},
"sizes": {