mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-23 02:41:01 -07:00
Merge branch 'master' into feature_local_container_server
Contributes to issue CURA-4243.
This commit is contained in:
commit
dad99f5292
542 changed files with 35309 additions and 16367 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
|||
from UM.MimeTypeDatabase import MimeTypeDatabase
|
||||
from UM.Job import Job
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Util import parseBool
|
||||
from .WorkspaceDialog import WorkspaceDialog
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
|
@ -21,6 +22,8 @@ from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
|||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||
from cura.QualityManager import QualityManager
|
||||
|
||||
from configparser import ConfigParser
|
||||
import zipfile
|
||||
|
|
@ -147,7 +150,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
if not definitions:
|
||||
definition_container = DefinitionContainer(container_id)
|
||||
definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"))
|
||||
definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), file_name = each_definition_container_file)
|
||||
definition_container = definition_container.getMetaData()
|
||||
|
||||
else:
|
||||
|
|
@ -202,7 +205,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
instance_container = InstanceContainer(container_id)
|
||||
|
||||
# Deserialize InstanceContainer by converting read data from bytes to string
|
||||
instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"))
|
||||
instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"),
|
||||
file_name = each_instance_container_file)
|
||||
instance_container_list.append(instance_container)
|
||||
|
||||
container_type = instance_container.getMetaDataEntry("type")
|
||||
|
|
@ -372,7 +376,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
return WorkspaceReader.PreReadResult.accepted
|
||||
|
||||
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
|
||||
def _overrideExtruderStack(self, global_stack, extruder_file_content):
|
||||
def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file):
|
||||
# Get extruder position first
|
||||
extruder_config = configparser.ConfigParser()
|
||||
extruder_config.read_string(extruder_file_content)
|
||||
|
|
@ -388,7 +392,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
return None
|
||||
|
||||
# Override the given extruder stack
|
||||
extruder_stack.deserialize(extruder_file_content)
|
||||
extruder_stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
|
||||
|
||||
# return the new ExtruderStack
|
||||
return extruder_stack
|
||||
|
|
@ -478,7 +482,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
|
||||
if not definitions:
|
||||
definition_container = DefinitionContainer(container_id)
|
||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
|
||||
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
|
||||
file_name = definition_container_file)
|
||||
self._container_registry.addContainer(definition_container)
|
||||
Job.yieldThread()
|
||||
|
||||
|
|
@ -496,18 +501,21 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
if not materials:
|
||||
material_container = xml_material_profile(container_id)
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
|
||||
file_name = material_container_file)
|
||||
containers_to_add.append(material_container)
|
||||
else:
|
||||
material_container = materials[0]
|
||||
if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only.
|
||||
if self._resolve_strategies["material"] == "override":
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
|
||||
file_name = material_container_file)
|
||||
elif self._resolve_strategies["material"] == "new":
|
||||
# Note that we *must* deserialize it with a new ID, as multiple containers will be
|
||||
# auto created & added.
|
||||
material_container = xml_material_profile(self.getNewId(container_id))
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
|
||||
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
|
||||
file_name = material_container_file)
|
||||
containers_to_add.append(material_container)
|
||||
|
||||
material_containers.append(material_container)
|
||||
|
|
@ -534,7 +542,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
instance_container = InstanceContainer(container_id)
|
||||
|
||||
# Deserialize InstanceContainer by converting read data from bytes to string
|
||||
instance_container.deserialize(serialized)
|
||||
instance_container.deserialize(serialized, file_name = instance_container_file)
|
||||
container_type = instance_container.getMetaDataEntry("type")
|
||||
Job.yieldThread()
|
||||
|
||||
|
|
@ -556,7 +564,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
else:
|
||||
if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
|
||||
instance_container = user_containers[0]
|
||||
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
|
||||
file_name = instance_container_file)
|
||||
instance_container.setDirty(True)
|
||||
elif self._resolve_strategies["machine"] == "new":
|
||||
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
|
||||
|
|
@ -589,7 +598,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# selected strategy.
|
||||
if self._resolve_strategies[container_type] == "override":
|
||||
instance_container = changes_containers[0]
|
||||
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
|
||||
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"),
|
||||
file_name = instance_container_file)
|
||||
instance_container.setDirty(True)
|
||||
|
||||
elif self._resolve_strategies[container_type] == "new":
|
||||
|
|
@ -638,9 +648,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]
|
||||
|
|
@ -649,7 +660,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# There is a machine, check if it has authentication data. If so, keep that data.
|
||||
network_authentication_id = stack.getMetaDataEntry("network_authentication_id")
|
||||
network_authentication_key = stack.getMetaDataEntry("network_authentication_key")
|
||||
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
|
||||
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"), file_name = global_stack_file)
|
||||
if network_authentication_id:
|
||||
stack.addMetaDataEntry("network_authentication_id", network_authentication_id)
|
||||
if network_authentication_key:
|
||||
|
|
@ -659,7 +670,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# create a new global stack
|
||||
stack = GlobalStack(global_stack_id_new)
|
||||
# Deserialize stack by converting read data from bytes to string
|
||||
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
|
||||
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"),
|
||||
file_name = global_stack_file)
|
||||
|
||||
# Ensure a unique ID and name
|
||||
stack._id = global_stack_id_new
|
||||
|
|
@ -676,12 +688,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:
|
||||
|
|
@ -691,16 +702,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:
|
||||
|
|
@ -708,11 +709,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
|
||||
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
if global_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
# deserialize new extruder stack over the current ones (if any)
|
||||
stack = self._overrideExtruderStack(global_stack, extruder_file_content)
|
||||
if stack is None:
|
||||
continue
|
||||
# deserialize new extruder stack over the current ones (if any)
|
||||
stack = self._overrideExtruderStack(global_stack, extruder_file_content, extruder_stack_file)
|
||||
if stack is None:
|
||||
continue
|
||||
|
||||
elif self._resolve_strategies["machine"] == "new":
|
||||
new_id = extruder_stack_id_map[container_id]
|
||||
|
|
@ -730,7 +730,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
extruder_config.write(tmp_string_io)
|
||||
extruder_file_content = tmp_string_io.getvalue()
|
||||
|
||||
stack.deserialize(extruder_file_content)
|
||||
stack.deserialize(extruder_file_content, file_name = extruder_stack_file)
|
||||
|
||||
# Ensure a unique ID and name
|
||||
stack._id = new_id
|
||||
|
|
@ -743,9 +743,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
# Create a new definition_changes container if it was empty
|
||||
if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer():
|
||||
stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack._id + "_settings"))
|
||||
if global_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings"))
|
||||
|
||||
if stack.getMetaDataEntry("type") == "extruder_train":
|
||||
extruder_stacks.append(stack)
|
||||
|
||||
# If not extruder stacks were saved in the project file (pre 3.1) create one manually
|
||||
# We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this
|
||||
if not extruder_stacks:
|
||||
stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder")
|
||||
if stack:
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
# in case the extruder is newly created (for a single-extrusion machine), we need to override
|
||||
# the existing extruder stack.
|
||||
existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")]
|
||||
for idx in range(len(_ContainerIndexes.IndexTypeMap)):
|
||||
existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True)
|
||||
extruder_stacks.append(existing_extruder_stack)
|
||||
else:
|
||||
extruder_stacks.append(stack)
|
||||
|
||||
except:
|
||||
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
|
||||
# Something went really wrong. Try to remove any data that we added.
|
||||
|
|
@ -753,13 +770,117 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._container_registry.removeContainer(container.getId())
|
||||
return
|
||||
|
||||
|
||||
# Check quality profiles to make sure that if one stack has the "not supported" quality profile,
|
||||
# all others should have the same.
|
||||
#
|
||||
# This block code tries to fix the following problems in Cura 3.0 and earlier:
|
||||
# 1. The upgrade script can rename all "Not Supported" quality profiles to "empty_quality", but it cannot fix
|
||||
# the problem that the global stack the extruder stacks may have different quality profiles. The code
|
||||
# below loops over all stacks and make sure that if there is one stack with "Not Supported" profile, the
|
||||
# rest should also use the "Not Supported" profile.
|
||||
# 2. In earlier versions (at least 2.7 and 3.0), a wrong quality profile could be assigned to a stack. For
|
||||
# example, a UM3 can have a BB 0.8 variant with "aa04_pla_fast" quality profile enabled. To fix this,
|
||||
# in the code below we also check the actual available quality profiles for the machine.
|
||||
#
|
||||
has_not_supported = False
|
||||
for stack in [global_stack] + extruder_stacks:
|
||||
if stack.quality.getId() in ("empty", "empty_quality"):
|
||||
has_not_supported = True
|
||||
break
|
||||
available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack,
|
||||
extruder_stacks)
|
||||
if not has_not_supported:
|
||||
has_not_supported = not available_quality
|
||||
|
||||
quality_has_been_changed = False
|
||||
|
||||
if has_not_supported:
|
||||
empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
|
||||
for stack in [global_stack] + extruder_stacks:
|
||||
stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container)
|
||||
empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
|
||||
for stack in [global_stack] + extruder_stacks:
|
||||
stack.replaceContainer(_ContainerIndexes.QualityChanges, empty_quality_changes_container)
|
||||
quality_has_been_changed = True
|
||||
|
||||
else:
|
||||
empty_quality_changes_container = self._container_registry.findInstanceContainers(id="empty_quality_changes")[0]
|
||||
|
||||
# The machine in the project has non-empty quality and there are usable qualities for this machine.
|
||||
# We need to check if the current quality_type is still usable for this machine, if not, then the quality
|
||||
# will be reset to the "preferred quality" if present, otherwise "normal".
|
||||
available_quality_types = [q.getMetaDataEntry("quality_type") for q in available_quality]
|
||||
|
||||
if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types:
|
||||
# We are here because the quality_type specified in the project is not supported any more,
|
||||
# so we need to switch it to the "preferred quality" if present, otherwise "normal".
|
||||
quality_has_been_changed = True
|
||||
|
||||
# find the preferred quality
|
||||
preferred_quality_id = global_stack.getMetaDataEntry("preferred_quality", None)
|
||||
if preferred_quality_id is not None:
|
||||
definition_id = global_stack.definition.getId()
|
||||
definition_id = global_stack.definition.getMetaDataEntry("quality_definition", definition_id)
|
||||
if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
|
||||
definition_id = "fdmprinter"
|
||||
|
||||
containers = self._container_registry.findInstanceContainers(id = preferred_quality_id,
|
||||
type = "quality",
|
||||
definition = definition_id)
|
||||
containers = [c for c in containers if not c.getMetaDataEntry("material", "")]
|
||||
if containers:
|
||||
global_stack.quality = containers[0]
|
||||
global_stack.qualityChanges = empty_quality_changes_container
|
||||
# also find the quality containers for the extruders
|
||||
for extruder_stack in extruder_stacks:
|
||||
search_criteria = {"id": preferred_quality_id,
|
||||
"type": "quality",
|
||||
"definition": definition_id}
|
||||
if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
|
||||
search_criteria["material"] = extruder_stack.material.getId()
|
||||
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
extruder_stack.quality = containers[0]
|
||||
extruder_stack.qualityChanges = empty_quality_changes_container
|
||||
else:
|
||||
Logger.log("e", "Cannot find preferred quality for extruder [%s].", extruder_stack.getId())
|
||||
|
||||
else:
|
||||
# we cannot find the preferred quality. THIS SHOULD NOT HAPPEN
|
||||
Logger.log("e", "Cannot find the preferred quality for machine [%s]", global_stack.getId())
|
||||
else:
|
||||
# The quality_type specified in the project file is usable, but the quality container itself may not
|
||||
# be correct. For example, for UM2, the quality container can be "draft" while it should be "um2_draft"
|
||||
# instead.
|
||||
# In this code branch, we try to fix those incorrect quality containers.
|
||||
#
|
||||
# ***IMPORTANT***: We only do this fix for single-extrusion machines.
|
||||
# We will first find the correct quality profile for the extruder, then apply the same
|
||||
# quality profile for the global stack.
|
||||
#
|
||||
if len(extruder_stacks) == 1:
|
||||
extruder_stack = extruder_stacks[0]
|
||||
|
||||
search_criteria = {"type": "quality",
|
||||
"quality_type": global_stack.quality.getMetaDataEntry("quality_type")}
|
||||
search_criteria["definition"] = global_stack.definition.getId()
|
||||
if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")):
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"):
|
||||
search_criteria["material"] = extruder_stack.material.getId()
|
||||
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
new_quality_container = containers[0]
|
||||
extruder_stack.quality = new_quality_container
|
||||
global_stack.quality = new_quality_container
|
||||
|
||||
# Replacing the old containers if resolve is "new".
|
||||
# When resolve is "new", some containers will get renamed, so all the other containers that reference to those
|
||||
# MUST get updated too.
|
||||
#
|
||||
if self._resolve_strategies["machine"] == "new":
|
||||
|
||||
# A new machine was made, but it was serialized with the wrong user container. Fix that now.
|
||||
for container in user_instance_containers:
|
||||
# replacing the container ID for user instance containers for the extruders
|
||||
|
|
@ -777,7 +898,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
global_stack.userChanges = container
|
||||
continue
|
||||
|
||||
for changes_container_type in ("quality_changes", "definition_changes"):
|
||||
changes_container_types = ("quality_changes", "definition_changes")
|
||||
if quality_has_been_changed:
|
||||
# DO NOT replace quality_changes if the current quality_type is not supported
|
||||
changes_container_types = ("definition_changes",)
|
||||
for changes_container_type in changes_container_types:
|
||||
if self._resolve_strategies[changes_container_type] == "new":
|
||||
# Quality changes needs to get a new ID, added to registry and to the right stacks
|
||||
for each_changes_container in quality_and_definition_changes_instance_containers:
|
||||
|
|
@ -877,7 +1002,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
base_file_name = os.path.basename(file_name)
|
||||
if base_file_name.endswith(".curaproject.3mf"):
|
||||
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
|
||||
Application.getInstance().projectFileLoaded.emit(base_file_name)
|
||||
self.setWorkspaceName(base_file_name)
|
||||
return nodes
|
||||
|
||||
## HACK: Replaces the material container in the given stack with a newly created material container.
|
||||
|
|
|
|||
|
|
@ -256,14 +256,8 @@ class WorkspaceDialog(QObject):
|
|||
return self._result
|
||||
|
||||
def _createViewFromQML(self):
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url))
|
||||
self._component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
self._context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._context.setContextProperty("manager", self)
|
||||
self._view = self._component.create(self._context)
|
||||
if self._view is None:
|
||||
Logger.log("c", "QQmlComponent status %s", self._component.status())
|
||||
Logger.log("c", "QQmlComponent error string %s", self._component.errorString())
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url)
|
||||
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
def show(self):
|
||||
# Emit signal so the right thread actually shows the view.
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
if stack is not None:
|
||||
changed_setting_keys = set(stack.getTop().getAllKeys())
|
||||
|
||||
# Ensure that we save the extruder used for this object.
|
||||
# Ensure that we save the extruder used for this object in a multi-extrusion setup
|
||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
changed_setting_keys.add("extruder_nr")
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from UM.Application import Application
|
|||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Version import Version
|
||||
|
||||
from PyQt5.QtQuick import QQuickView
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, QObject
|
||||
|
||||
|
|
@ -107,9 +106,5 @@ class ChangeLog(Extension, QObject,):
|
|||
self._changelog_window.hide()
|
||||
|
||||
def createChangelogWindow(self):
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml"))
|
||||
|
||||
component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
self._changelog_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._changelog_context.setContextProperty("manager", self)
|
||||
self._changelog_window = component.create(self._changelog_context)
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")
|
||||
self._changelog_window = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
[3.0.4]
|
||||
*Bug fixes
|
||||
- Fixed OpenGL issue that prevents Cura from starting.
|
||||
|
||||
*License agreement on the first startup has been added
|
||||
|
||||
[3.0.3]
|
||||
*Bug fixes
|
||||
- Add missing libraries for the MakePrintable plugin.
|
||||
|
||||
[3.0.0]
|
||||
*Faster start-up
|
||||
Start-up speed has been cut in half compared to the previous version.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
error_labels.add(definitions[0].label)
|
||||
|
||||
error_labels = ", ".join(error_labels)
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels)),
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(error_labels),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
|
|
@ -301,6 +301,26 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
return
|
||||
|
||||
elif job.getResult() == StartSliceJob.StartJobResult.ObjectSettingError:
|
||||
errors = {}
|
||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
||||
stack = node.callDecoration("getStack")
|
||||
if not stack:
|
||||
continue
|
||||
for key in stack.getErrorKeys():
|
||||
definition = self._global_container_stack.getBottom().findDefinitions(key = key)
|
||||
if not definition:
|
||||
Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key))
|
||||
continue
|
||||
definition = definition[0]
|
||||
errors[key] = definition.label
|
||||
error_labels = ", ".join(errors.values())
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = error_labels),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError:
|
||||
if Application.getInstance().platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
|
||||
|
|
@ -588,7 +608,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
def _onActiveViewChanged(self):
|
||||
if Application.getInstance().getController().getActiveView():
|
||||
view = Application.getInstance().getController().getActiveView()
|
||||
if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
||||
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
||||
self._layer_view_active = True
|
||||
# There is data and we're not slicing at the moment
|
||||
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
|
||||
def run(self):
|
||||
start_time = time()
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
|
||||
self._progress_message.show()
|
||||
Job.yieldThread()
|
||||
if self._abort_requested:
|
||||
|
|
@ -95,20 +95,27 @@ class ProcessSlicedLayersJob(Job):
|
|||
|
||||
# Find the minimum layer number
|
||||
# When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
|
||||
# instead simply offset all other layers so the lowest layer is always 0.
|
||||
# instead simply offset all other layers so the lowest layer is always 0. It could happens that
|
||||
# the first raft layer has value -8 but there are just 4 raft (negative) layers.
|
||||
min_layer_number = 0
|
||||
negative_layers = 0
|
||||
for layer in self._layers:
|
||||
if layer.id < min_layer_number:
|
||||
min_layer_number = layer.id
|
||||
if layer.id < 0:
|
||||
negative_layers += 1
|
||||
|
||||
current_layer = 0
|
||||
|
||||
for layer in self._layers:
|
||||
abs_layer_number = layer.id + abs(min_layer_number)
|
||||
# Negative layers are offset by the minimum layer number, but the positive layers are just
|
||||
# offset by the number of negative layers so there is no layer gap between raft and model
|
||||
abs_layer_number = layer.id + abs(min_layer_number) if layer.id < 0 else layer.id + negative_layers
|
||||
|
||||
layer_data.addLayer(abs_layer_number)
|
||||
this_layer = layer_data.getLayer(abs_layer_number)
|
||||
layer_data.setLayerHeight(abs_layer_number, layer.height)
|
||||
layer_data.setLayerThickness(abs_layer_number, layer.thickness)
|
||||
|
||||
for p in range(layer.repeatedMessageCount("path_segment")):
|
||||
polygon = layer.getRepeatedMessage("path_segment", p)
|
||||
|
|
@ -127,10 +134,11 @@ class ProcessSlicedLayersJob(Job):
|
|||
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
|
||||
line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||
|
||||
# In the future, line_thicknesses should be given by CuraEngine as well.
|
||||
# Currently the infill layer thickness also translates to line width
|
||||
line_thicknesses = numpy.zeros(line_widths.shape, dtype="f4")
|
||||
line_thicknesses[:] = layer.thickness / 1000 # from micrometer to millimeter
|
||||
line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array
|
||||
line_thicknesses = line_thicknesses.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||
|
||||
line_feedrates = numpy.fromstring(polygon.line_feedrate, dtype="f4") # Convert bytearray to numpy array
|
||||
line_feedrates = line_feedrates.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
|
||||
|
||||
# Create a new 3D-array, copy the 2D points over and insert the right height.
|
||||
# This uses manual array creation + copy rather than numpy.insert since this is
|
||||
|
|
@ -145,7 +153,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
new_points[:, 1] = points[:, 2]
|
||||
new_points[:, 2] = -points[:, 1]
|
||||
|
||||
this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses)
|
||||
this_poly = LayerPolygon.LayerPolygon(extruder, line_types, new_points, line_widths, line_thicknesses, line_feedrates)
|
||||
this_poly.buildCache()
|
||||
|
||||
this_layer.polygons.append(this_poly)
|
||||
|
|
@ -219,7 +227,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
self._progress_message.setProgress(100)
|
||||
|
||||
view = Application.getInstance().getController().getActiveView()
|
||||
if view.getPluginId() == "LayerView":
|
||||
if view.getPluginId() == "SimulationView":
|
||||
view.resetLayerData()
|
||||
|
||||
if self._progress_message:
|
||||
|
|
@ -232,7 +240,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
|
||||
def _onActiveViewChanged(self):
|
||||
if self.isRunning():
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
|
||||
if Application.getInstance().getController().getActiveView().getPluginId() == "SimulationView":
|
||||
if not self._progress_message:
|
||||
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, 0, catalog.i18nc("@info:title", "Information"))
|
||||
if self._progress_message.getProgress() != 100:
|
||||
|
|
@ -240,3 +248,4 @@ class ProcessSlicedLayersJob(Job):
|
|||
else:
|
||||
if self._progress_message:
|
||||
self._progress_message.hide()
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class StartJobResult(IntEnum):
|
|||
NothingToSlice = 4
|
||||
MaterialIncompatible = 5
|
||||
BuildPlateError = 6
|
||||
ObjectSettingError = 7 #When an error occurs in per-object settings.
|
||||
|
||||
|
||||
## Formatter class that handles token expansion in start/end gcod
|
||||
|
|
@ -105,7 +106,7 @@ class StartSliceJob(Job):
|
|||
continue
|
||||
|
||||
if self._checkStackForErrors(node.callDecoration("getStack")):
|
||||
self.setResult(StartJobResult.SettingError)
|
||||
self.setResult(StartJobResult.ObjectSettingError)
|
||||
return
|
||||
|
||||
with self._scene.getSceneLock():
|
||||
|
|
@ -158,13 +159,9 @@ class StartSliceJob(Job):
|
|||
self._buildGlobalSettingsMessage(stack)
|
||||
self._buildGlobalInheritsStackMessage(stack)
|
||||
|
||||
# Only add extruder stacks if there are multiple extruders
|
||||
# Single extruder machines only use the global stack to store setting values
|
||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
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")
|
||||
|
|
@ -250,19 +247,6 @@ class StartSliceJob(Job):
|
|||
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
|
||||
Job.yieldThread()
|
||||
|
||||
## Create extruder message from global stack
|
||||
def _buildExtruderMessageFromGlobalStack(self, stack):
|
||||
message = self._slice_message.addRepeatedMessage("extruders")
|
||||
|
||||
for key in stack.getAllKeys():
|
||||
# Do not send settings that are not settable_per_extruder.
|
||||
if not stack.getProperty(key, "settable_per_extruder"):
|
||||
continue
|
||||
setting = message.getMessage("settings").addRepeatedMessage("settings")
|
||||
setting.name = key
|
||||
setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
|
||||
Job.yieldThread()
|
||||
|
||||
## Sends all global settings to the engine.
|
||||
#
|
||||
# The settings are taken from the global stack. This does not include any
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Logger import Logger
|
||||
|
|
@ -32,6 +35,17 @@ class FirmwareUpdateChecker(Extension):
|
|||
if Preferences.getInstance().getValue("info/automatic_update_check"):
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
self._download_url = None
|
||||
|
||||
## Callback for the message that is spawned when there is a new version.
|
||||
def _onActionTriggered(self, message, action):
|
||||
if action == "download":
|
||||
if self._download_url is not None:
|
||||
QDesktopServices.openUrl(QUrl(self._download_url))
|
||||
|
||||
def _onSetDownloadUrl(self, download_url):
|
||||
self._download_url = download_url
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
# Only take care when a new GlobalStack was added
|
||||
if isinstance(container, GlobalStack):
|
||||
|
|
@ -45,5 +59,7 @@ class FirmwareUpdateChecker(Extension):
|
|||
# \param silent type(boolean) Suppresses messages other than "new version found" messages.
|
||||
# This is used when checking for a new firmware version at startup.
|
||||
def checkFirmwareVersion(self, container = None, silent = False):
|
||||
job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL)
|
||||
job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL,
|
||||
callback = self._onActionTriggered,
|
||||
set_download_url_callback = self._onSetDownloadUrl)
|
||||
job.start()
|
||||
|
|
|
|||
|
|
@ -10,30 +10,21 @@ from UM.Job import Job
|
|||
import urllib.request
|
||||
import codecs
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## This job checks if there is an update available on the provided URL.
|
||||
class FirmwareUpdateCheckerJob(Job):
|
||||
def __init__(self, container = None, silent = False, url = None):
|
||||
def __init__(self, container = None, silent = False, url = None, callback = None, set_download_url_callback = None):
|
||||
super().__init__()
|
||||
self._container = container
|
||||
self.silent = silent
|
||||
self._url = url
|
||||
self._download_url = None # If an update was found, the download_url will be set to the location of the new version.
|
||||
|
||||
## Callback for the message that is spawned when there is a new version.
|
||||
def actionTriggered(self, message, action):
|
||||
if action == "download":
|
||||
if self._download_url is not None:
|
||||
QDesktopServices.openUrl(QUrl(self._download_url))
|
||||
self._callback = callback
|
||||
self._set_download_url_callback = set_download_url_callback
|
||||
|
||||
def run(self):
|
||||
self._download_url = None # Reset download ur.
|
||||
if not self._url:
|
||||
Logger.log("e", "Can not check for a new release. URL not set!")
|
||||
return
|
||||
|
|
@ -70,13 +61,14 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
# notify the user when no new firmware version is available.
|
||||
if (checked_version != "") and (checked_version != current_version):
|
||||
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
|
||||
message = Message(i18n_catalog.i18nc("@info Don't translate {machine_name}, since it gets replaced by a printer name!", "To ensure that your {machine_name} is equipped with the latest features it is recommended to update the firmware regularly. This can be done on the {machine_name} (when connected to the network) or via USB.").format(machine_name = machine_name),
|
||||
message = Message(i18n_catalog.i18nc("@info Don't translate {machine_name}, since it gets replaced by a printer name!", "New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(machine_name = machine_name),
|
||||
title = i18n_catalog.i18nc("@info:title The %s gets replaced with the printer name.", "New %s firmware available") % machine_name)
|
||||
message.addAction("download", i18n_catalog.i18nc("@action:button", "Download"), "[no_icon]", "[no_description]")
|
||||
message.addAction("download", i18n_catalog.i18nc("@action:button", "How to update"), "[no_icon]", "[no_description]")
|
||||
|
||||
# If we do this in a cool way, the download url should be available in the JSON file
|
||||
self._download_url = "https://ultimaker.com/en/resources/20500-upgrade-firmware"
|
||||
message.actionTriggered.connect(self.actionTriggered)
|
||||
if self._set_download_url_callback:
|
||||
self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
|
||||
message.actionTriggered.connect(self._callback)
|
||||
message.show()
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2016 Aleph Objects, Inc.
|
||||
# Copyright (c) 2017 Aleph Objects, Inc.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
|
|
@ -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,16 @@ 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)
|
||||
self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83)
|
||||
|
||||
@staticmethod
|
||||
def _getValue(line, code):
|
||||
|
|
@ -96,7 +101,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 +119,89 @@ 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 _createEmptyLayer(self, layer_number):
|
||||
self._layer_data_builder.addLayer(layer_number)
|
||||
self._layer_data_builder.setLayerHeight(layer_number, 0)
|
||||
self._layer_data_builder.setLayerThickness(layer_number, 0)
|
||||
|
||||
def _calculateLineWidth(self, current_point, previous_point, current_extrusion, previous_extrusion, layer_thickness):
|
||||
# Area of the filament
|
||||
Af = (self._filament_diameter / 2) ** 2 * numpy.pi
|
||||
# Length of the extruded filament
|
||||
de = current_extrusion - previous_extrusion
|
||||
# Volumne of the extruded filament
|
||||
dVe = de * Af
|
||||
# Length of the printed line
|
||||
dX = numpy.sqrt((current_point[0] - previous_point[0])**2 + (current_point[2] - previous_point[2])**2)
|
||||
# When the extruder recovers from a retraction, we get zero distance
|
||||
if dX == 0:
|
||||
return 0.1
|
||||
# Area of the printed line. This area is a rectangle
|
||||
Ae = dVe / dX
|
||||
# This area is a rectangle with area equal to layer_thickness * layer_width
|
||||
line_width = Ae / layer_thickness
|
||||
|
||||
# A threshold is set to avoid weird paths in the GCode
|
||||
if line_width > 1.2:
|
||||
return 0.35
|
||||
return line_width
|
||||
|
||||
def _gCode0(self, position, params, path):
|
||||
x, y, z, 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_extrusion else e[self._extruder_number] + params.e
|
||||
if new_extrusion_value > e[self._extruder_number]:
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
|
||||
else:
|
||||
path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction
|
||||
e[self._extruder_number] = params.e
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
|
||||
e[self._extruder_number] = new_extrusion_value
|
||||
|
||||
# Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions
|
||||
# Also, 1.5 is a heuristic for any priming or whatsoever, we skip those.
|
||||
if z > self._previous_z and (z - self._previous_z < 1.5):
|
||||
self._current_layer_thickness = z - self._previous_z + 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 +212,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 +244,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,20 +256,31 @@ 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
|
||||
|
||||
def _processMCode(self, M):
|
||||
if M == 82:
|
||||
# Set absolute extrusion mode
|
||||
self._is_absolute_extrusion = True
|
||||
elif M == 83:
|
||||
# Set relative extrusion mode
|
||||
self._is_absolute_extrusion = False
|
||||
|
||||
_type_keyword = ";TYPE:"
|
||||
_layer_keyword = ";LAYER:"
|
||||
|
||||
|
|
@ -223,6 +296,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,8 +335,11 @@ 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 = []
|
||||
min_layer_number = 0
|
||||
negative_layers = 0
|
||||
previous_layer = 0
|
||||
|
||||
for line in file:
|
||||
if self._cancelled:
|
||||
|
|
@ -293,12 +371,29 @@ 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):])
|
||||
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
|
||||
current_path.clear()
|
||||
|
||||
# When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior
|
||||
# as in ProcessSlicedLayersJob
|
||||
if layer_number < min_layer_number:
|
||||
min_layer_number = layer_number
|
||||
if layer_number < 0:
|
||||
layer_number += abs(min_layer_number)
|
||||
negative_layers += 1
|
||||
else:
|
||||
layer_number += negative_layers
|
||||
|
||||
# In case there is a gap in the layer count, empty layers are created
|
||||
for empty_layer in range(previous_layer + 1, layer_number):
|
||||
self._createEmptyLayer(empty_layer)
|
||||
|
||||
self._layer_number = layer_number
|
||||
previous_layer = layer_number
|
||||
except:
|
||||
pass
|
||||
|
||||
|
|
@ -308,17 +403,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 +417,12 @@ 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:
|
||||
if line.startswith("M"):
|
||||
M = self._getInt(line, "M")
|
||||
self._processMCode(M)
|
||||
|
||||
# "Flush" leftovers. Last layer paths are still stored
|
||||
if len(current_path) > 1:
|
||||
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
|
||||
self._layer_number += 1
|
||||
current_path.clear()
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.ToolHandle import ToolHandle
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from UM.View.RenderPass import RenderPass
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
|
||||
import os.path
|
||||
|
||||
## RenderPass used to display g-code paths.
|
||||
class LayerPass(RenderPass):
|
||||
def __init__(self, width, height):
|
||||
super().__init__("layerview", width, height)
|
||||
|
||||
self._layer_shader = None
|
||||
self._tool_handle_shader = None
|
||||
self._gl = OpenGL.getInstance().getBindingsObject()
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._extruder_manager = ExtruderManager.getInstance()
|
||||
|
||||
self._layer_view = None
|
||||
self._compatibility_mode = None
|
||||
|
||||
def setLayerView(self, layerview):
|
||||
self._layer_view = layerview
|
||||
self._compatibility_mode = layerview.getCompatibilityMode()
|
||||
|
||||
def render(self):
|
||||
if not self._layer_shader:
|
||||
if self._compatibility_mode:
|
||||
shader_filename = "layers.shader"
|
||||
else:
|
||||
shader_filename = "layers3d.shader"
|
||||
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), shader_filename))
|
||||
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
|
||||
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
|
||||
if self._layer_view:
|
||||
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getLayerViewType())
|
||||
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
|
||||
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
|
||||
self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers())
|
||||
self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
|
||||
self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
|
||||
else:
|
||||
#defaults
|
||||
self._layer_shader.setUniformValue("u_layer_view_type", 1)
|
||||
self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1])
|
||||
self._layer_shader.setUniformValue("u_show_travel_moves", 0)
|
||||
self._layer_shader.setUniformValue("u_show_helpers", 1)
|
||||
self._layer_shader.setUniformValue("u_show_skin", 1)
|
||||
self._layer_shader.setUniformValue("u_show_infill", 1)
|
||||
|
||||
if not self._tool_handle_shader:
|
||||
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
|
||||
|
||||
self.bind()
|
||||
|
||||
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
|
||||
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
|
||||
if isinstance(node, ToolHandle):
|
||||
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
|
||||
|
||||
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
||||
# Render all layers below a certain number as line mesh instead of vertices.
|
||||
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
|
||||
start = 0
|
||||
end = 0
|
||||
element_counts = layer_data.getElementCounts()
|
||||
for layer in sorted(element_counts.keys()):
|
||||
if layer > self._layer_view._current_layer_num:
|
||||
break
|
||||
if self._layer_view._minimum_layer_num > layer:
|
||||
start += element_counts[layer]
|
||||
end += element_counts[layer]
|
||||
|
||||
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
||||
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
|
||||
batch.addItem(node.getWorldTransformation(), layer_data)
|
||||
batch.render(self._scene.getActiveCamera())
|
||||
|
||||
# Create a new batch that is not range-limited
|
||||
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
|
||||
|
||||
if self._layer_view.getCurrentLayerMesh():
|
||||
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh())
|
||||
|
||||
if self._layer_view.getCurrentLayerJumps():
|
||||
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps())
|
||||
|
||||
if len(batch.items) > 0:
|
||||
batch.render(self._scene.getActiveCamera())
|
||||
|
||||
# Render toolhandles on top of the layerview
|
||||
if len(tool_handle_batch.items) > 0:
|
||||
tool_handle_batch.render(self._scene.getActiveCamera())
|
||||
|
||||
self.release()
|
||||
|
|
@ -1,388 +0,0 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.4
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
|
||||
import UM 1.0 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
width: {
|
||||
if (UM.LayerView.compatibilityMode) {
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
||||
} else {
|
||||
return UM.Theme.getSize("layerview_menu_size").width;
|
||||
}
|
||||
}
|
||||
height: {
|
||||
if (UM.LayerView.compatibilityMode) {
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
|
||||
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
|
||||
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||
} else {
|
||||
return UM.Theme.getSize("layerview_menu_size").height + UM.LayerView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||
}
|
||||
}
|
||||
|
||||
property var buttonTarget: {
|
||||
if(parent != null)
|
||||
{
|
||||
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
||||
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
|
||||
}
|
||||
return Qt.point(0,0)
|
||||
}
|
||||
|
||||
visible: parent != null ? !parent.parent.monitoringPrint: true
|
||||
|
||||
UM.PointingRectangle {
|
||||
id: layerViewMenu
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
z: slider.z - 1
|
||||
color: UM.Theme.getColor("tool_panel_background")
|
||||
borderWidth: UM.Theme.getSize("default_lining").width
|
||||
borderColor: UM.Theme.getColor("lining")
|
||||
arrowSize: 0 // hide arrow until weird issue with first time rendering is fixed
|
||||
|
||||
ColumnLayout {
|
||||
id: view_settings
|
||||
|
||||
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
|
||||
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
|
||||
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
|
||||
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
|
||||
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
|
||||
// if we are in compatibility mode, we only show the "line type"
|
||||
property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1
|
||||
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
|
||||
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
spacing: UM.Theme.getSize("layerview_row_spacing").height
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Label
|
||||
{
|
||||
id: layerViewTypesLabel
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@label","Color scheme")
|
||||
font: UM.Theme.getFont("default");
|
||||
visible: !UM.LayerView.compatibilityMode
|
||||
Layout.fillWidth: true
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
}
|
||||
|
||||
ListModel // matches LayerView.py
|
||||
{
|
||||
id: layerViewTypes
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
layerViewTypes.append({
|
||||
text: catalog.i18nc("@label:listbox", "Material Color"),
|
||||
type_id: 0
|
||||
})
|
||||
layerViewTypes.append({
|
||||
text: catalog.i18nc("@label:listbox", "Line Type"),
|
||||
type_id: 1 // these ids match the switching in the shader
|
||||
})
|
||||
}
|
||||
|
||||
ComboBox
|
||||
{
|
||||
id: layerTypeCombobox
|
||||
anchors.left: parent.left
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
model: layerViewTypes
|
||||
visible: !UM.LayerView.compatibilityMode
|
||||
style: UM.Theme.styles.combobox
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10 * screenScaleFactor
|
||||
|
||||
onActivated:
|
||||
{
|
||||
UM.Preferences.setValue("layerview/layer_view_type", index);
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
|
||||
updateLegends(currentIndex);
|
||||
}
|
||||
|
||||
function updateLegends(type_id)
|
||||
{
|
||||
// update visibility of legends
|
||||
view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: compatibilityModeLabel
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@label","Compatibility Mode")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
visible: UM.LayerView.compatibilityMode
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: space2Label
|
||||
anchors.left: parent.left
|
||||
text: " "
|
||||
font.pointSize: 0.5
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: UM.Preferences
|
||||
onPreferenceChanged:
|
||||
{
|
||||
layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
|
||||
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
|
||||
view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
|
||||
view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
|
||||
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
|
||||
view_settings.show_skin = UM.Preferences.getValue("layerview/show_skin");
|
||||
view_settings.show_infill = UM.Preferences.getValue("layerview/show_infill");
|
||||
view_settings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
|
||||
view_settings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Cura.ExtrudersModel{}
|
||||
CheckBox {
|
||||
id: extrudersModelCheckBox
|
||||
checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
|
||||
onClicked: {
|
||||
view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
||||
UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
|
||||
}
|
||||
visible: !UM.LayerView.compatibilityMode
|
||||
enabled: index + 1 <= 4
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: extrudersModelCheckBox.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
color: model.color
|
||||
radius: width / 2
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: !view_settings.show_legend
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
style: UM.Theme.styles.checkbox
|
||||
Label
|
||||
{
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: extrudersModelCheckBox.left;
|
||||
anchors.right: extrudersModelCheckBox.right;
|
||||
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
id: typesLegenModel
|
||||
Component.onCompleted:
|
||||
{
|
||||
typesLegenModel.append({
|
||||
label: catalog.i18nc("@label", "Show Travels"),
|
||||
initialValue: view_settings.show_travel_moves,
|
||||
preference: "layerview/show_travel_moves",
|
||||
colorId: "layerview_move_combing"
|
||||
});
|
||||
typesLegenModel.append({
|
||||
label: catalog.i18nc("@label", "Show Helpers"),
|
||||
initialValue: view_settings.show_helpers,
|
||||
preference: "layerview/show_helpers",
|
||||
colorId: "layerview_support"
|
||||
});
|
||||
typesLegenModel.append({
|
||||
label: catalog.i18nc("@label", "Show Shell"),
|
||||
initialValue: view_settings.show_skin,
|
||||
preference: "layerview/show_skin",
|
||||
colorId: "layerview_inset_0"
|
||||
});
|
||||
typesLegenModel.append({
|
||||
label: catalog.i18nc("@label", "Show Infill"),
|
||||
initialValue: view_settings.show_infill,
|
||||
preference: "layerview/show_infill",
|
||||
colorId: "layerview_infill"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: legendModelCheckBox
|
||||
checked: model.initialValue
|
||||
onClicked: {
|
||||
UM.Preferences.setValue(model.preference, checked);
|
||||
}
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: legendModelCheckBox.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
color: UM.Theme.getColor(model.colorId)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: view_settings.show_legend
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
style: UM.Theme.styles.checkbox
|
||||
Label
|
||||
{
|
||||
text: label
|
||||
font: UM.Theme.getFont("default")
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: legendModelCheckBox.left;
|
||||
anchors.right: legendModelCheckBox.right;
|
||||
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
checked: view_settings.only_show_top_layers
|
||||
onClicked: {
|
||||
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
|
||||
}
|
||||
text: catalog.i18nc("@label", "Only Show Top Layers")
|
||||
visible: UM.LayerView.compatibilityMode
|
||||
style: UM.Theme.styles.checkbox
|
||||
}
|
||||
CheckBox {
|
||||
checked: view_settings.top_layer_count == 5
|
||||
onClicked: {
|
||||
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
|
||||
}
|
||||
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
||||
visible: UM.LayerView.compatibilityMode
|
||||
style: UM.Theme.styles.checkbox
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
id: typesLegenModelNoCheck
|
||||
Component.onCompleted:
|
||||
{
|
||||
typesLegenModelNoCheck.append({
|
||||
label: catalog.i18nc("@label", "Top / Bottom"),
|
||||
colorId: "layerview_skin",
|
||||
});
|
||||
typesLegenModelNoCheck.append({
|
||||
label: catalog.i18nc("@label", "Inner Wall"),
|
||||
colorId: "layerview_inset_x",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: label
|
||||
visible: view_settings.show_legend
|
||||
id: typesLegendModelLabel
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: typesLegendModelLabel.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
color: UM.Theme.getColor(model.colorId)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: view_settings.show_legend
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LayerSlider {
|
||||
id: slider
|
||||
|
||||
width: UM.Theme.getSize("slider_handle").width
|
||||
height: UM.Theme.getSize("layerview_menu_size").height
|
||||
|
||||
anchors {
|
||||
top: parent.bottom
|
||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||
right: layerViewMenu.right
|
||||
rightMargin: UM.Theme.getSize("slider_layerview_margin").width
|
||||
}
|
||||
|
||||
// custom properties
|
||||
upperValue: UM.LayerView.currentLayer
|
||||
lowerValue: UM.LayerView.minimumLayer
|
||||
maximumValue: UM.LayerView.numLayers
|
||||
handleSize: UM.Theme.getSize("slider_handle").width
|
||||
trackThickness: UM.Theme.getSize("slider_groove").width
|
||||
trackColor: UM.Theme.getColor("slider_groove")
|
||||
trackBorderColor: UM.Theme.getColor("slider_groove_border")
|
||||
upperHandleColor: UM.Theme.getColor("slider_handle")
|
||||
lowerHandleColor: UM.Theme.getColor("slider_handle")
|
||||
rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
|
||||
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
|
||||
layersVisible: UM.LayerView.layerActivity && CuraApplication.platformActivity ? true : false
|
||||
|
||||
// update values when layer data changes
|
||||
Connections {
|
||||
target: UM.LayerView
|
||||
onMaxLayersChanged: slider.setUpperValue(UM.LayerView.currentLayer)
|
||||
onMinimumLayerChanged: slider.setLowerValue(UM.LayerView.minimumLayer)
|
||||
onCurrentLayerChanged: slider.setUpperValue(UM.LayerView.currentLayer)
|
||||
}
|
||||
|
||||
// make sure the slider handlers show the correct value after switching views
|
||||
Component.onCompleted: {
|
||||
slider.setLowerValue(UM.LayerView.minimumLayer)
|
||||
slider.setUpperValue(UM.LayerView.currentLayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Application import Application
|
||||
|
||||
import LayerView
|
||||
|
||||
|
||||
class LayerViewProxy(QObject):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._current_layer = 0
|
||||
self._controller = Application.getInstance().getController()
|
||||
self._controller.activeViewChanged.connect(self._onActiveViewChanged)
|
||||
self._onActiveViewChanged()
|
||||
|
||||
currentLayerChanged = pyqtSignal()
|
||||
maxLayersChanged = pyqtSignal()
|
||||
activityChanged = pyqtSignal()
|
||||
globalStackChanged = pyqtSignal()
|
||||
preferencesChanged = pyqtSignal()
|
||||
busyChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify=activityChanged)
|
||||
def layerActivity(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.getActivity()
|
||||
|
||||
@pyqtProperty(int, notify=maxLayersChanged)
|
||||
def numLayers(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.getMaxLayers()
|
||||
|
||||
@pyqtProperty(int, notify=currentLayerChanged)
|
||||
def currentLayer(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.getCurrentLayer()
|
||||
|
||||
@pyqtProperty(int, notify=currentLayerChanged)
|
||||
def minimumLayer(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.getMinimumLayer()
|
||||
|
||||
@pyqtProperty(bool, notify=busyChanged)
|
||||
def busy(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.isBusy()
|
||||
|
||||
return False
|
||||
|
||||
@pyqtProperty(bool, notify=preferencesChanged)
|
||||
def compatibilityMode(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.getCompatibilityMode()
|
||||
return False
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setCurrentLayer(self, layer_num):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setLayer(layer_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setMinimumLayer(self, layer_num):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setMinimumLayer(layer_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setLayerViewType(self, layer_view_type):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setLayerViewType(layer_view_type)
|
||||
|
||||
@pyqtSlot(result=int)
|
||||
def getLayerViewType(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.getLayerViewType()
|
||||
return 0
|
||||
|
||||
# Opacity 0..1
|
||||
@pyqtSlot(int, float)
|
||||
def setExtruderOpacity(self, extruder_nr, opacity):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setExtruderOpacity(extruder_nr, opacity)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowTravelMoves(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setShowTravelMoves(show)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowHelpers(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setShowHelpers(show)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowSkin(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setShowSkin(show)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowInfill(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.setShowInfill(show)
|
||||
|
||||
@pyqtProperty(int, notify=globalStackChanged)
|
||||
def extruderCount(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
return active_view.getExtruderCount()
|
||||
return 0
|
||||
|
||||
def _layerActivityChanged(self):
|
||||
self.activityChanged.emit()
|
||||
|
||||
def _onLayerChanged(self):
|
||||
self.currentLayerChanged.emit()
|
||||
self._layerActivityChanged()
|
||||
|
||||
def _onMaxLayersChanged(self):
|
||||
self.maxLayersChanged.emit()
|
||||
|
||||
def _onBusyChanged(self):
|
||||
self.busyChanged.emit()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
self.globalStackChanged.emit()
|
||||
|
||||
def _onPreferencesChanged(self):
|
||||
self.preferencesChanged.emit()
|
||||
|
||||
def _onActiveViewChanged(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if type(active_view) == LayerView.LayerView.LayerView:
|
||||
active_view.currentLayerNumChanged.connect(self._onLayerChanged)
|
||||
active_view.maxLayersChanged.connect(self._onMaxLayersChanged)
|
||||
active_view.busyChanged.connect(self._onBusyChanged)
|
||||
active_view.globalStackChanged.connect(self._onGlobalStackChanged)
|
||||
active_view.preferencesChanged.connect(self._onPreferencesChanged)
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import LayerView, LayerViewProxy
|
||||
from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"view": {
|
||||
"name": catalog.i18nc("@item:inlistbox", "Layer view"),
|
||||
"view_panel": "LayerView.qml",
|
||||
"weight": 2
|
||||
}
|
||||
}
|
||||
|
||||
def createLayerViewProxy(engine, script_engine):
|
||||
return LayerViewProxy.LayerViewProxy()
|
||||
|
||||
def register(app):
|
||||
layer_view = LayerView.LayerView()
|
||||
qmlRegisterSingletonType(LayerViewProxy.LayerViewProxy, "UM", 1, 0, "LayerView", layer_view.getProxy)
|
||||
return { "view": LayerView.LayerView() }
|
||||
|
|
@ -116,8 +116,7 @@ class MachineSettingsAction(MachineAction):
|
|||
|
||||
@pyqtSlot(int)
|
||||
def setMachineExtruderCount(self, extruder_count):
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
extruder_manager = Application.getInstance().getExtruderManager()
|
||||
|
||||
definition_changes_container = self._global_container_stack.definitionChanges
|
||||
if not self._global_container_stack or definition_changes_container == self._empty_container:
|
||||
|
|
@ -127,38 +126,16 @@ 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"]]
|
||||
# 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
|
||||
if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
|
||||
continue
|
||||
|
||||
# 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)
|
||||
|
||||
# Check to see if any features are set to print with an extruder that will no longer exist
|
||||
for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]:
|
||||
if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count - 1:
|
||||
Logger.log("i", "Lowering %s setting to match number of extruders", setting_key)
|
||||
self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count - 1)
|
||||
old_value = int(self._global_container_stack.userChanges.getProperty(setting_key, "value"))
|
||||
if old_value >= extruder_count:
|
||||
self._global_container_stack.userChanges.removeInstance(setting_key)
|
||||
Logger.log("d", "Reset [%s] because its old value [%s] is no longer valid ", setting_key, old_value)
|
||||
|
||||
# Check to see if any objects are set to print with an extruder that will no longer exist
|
||||
root_node = Application.getInstance().getController().getScene().getRoot()
|
||||
|
|
@ -171,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.
|
||||
|
|
@ -269,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:
|
||||
|
|
@ -288,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()
|
||||
|
|
@ -332,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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ UM.Dialog
|
|||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
Label
|
||||
{
|
||||
text: "<b>" + model.name + "</b> - " + model.author
|
||||
text: "<b>" + model.name + "</b>" + ((model.author !== "") ? (" - " + model.author) : "")
|
||||
width: contentWidth
|
||||
height: contentHeight + UM.Theme.getSize("default_margin").height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
from . import RemovableDrivePlugin
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
|
|
@ -15,12 +13,12 @@ import plistlib
|
|||
class OSXRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
|
||||
def checkRemovableDrives(self):
|
||||
drives = {}
|
||||
p = subprocess.Popen(["system_profiler", "SPUSBDataType", "-xml"], stdout = subprocess.PIPE)
|
||||
p = subprocess.Popen(["system_profiler", "SPUSBDataType", "-xml"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
|
||||
plist = plistlib.loads(p.communicate()[0])
|
||||
|
||||
result = self._recursiveSearch(plist, "removable_media")
|
||||
|
||||
p = subprocess.Popen(["system_profiler", "SPCardReaderDataType", "-xml"], stdout=subprocess.PIPE)
|
||||
p = subprocess.Popen(["system_profiler", "SPCardReaderDataType", "-xml"], stdout=subprocess.PIPE, stderr = subprocess.PIPE)
|
||||
plist = plistlib.loads(p.communicate()[0])
|
||||
|
||||
result.extend(self._recursiveSearch(plist, "removable_media"))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
49
plugins/SimulationView/NozzleNode.py
Normal file
49
plugins/SimulationView/NozzleNode.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Math.Color import Color
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.Resources import Resources
|
||||
|
||||
import os
|
||||
|
||||
class NozzleNode(SceneNode):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._shader = None
|
||||
self.setCalculateBoundingBox(False)
|
||||
self._createNozzleMesh()
|
||||
|
||||
def _createNozzleMesh(self):
|
||||
mesh_file = "resources/nozzle.stl"
|
||||
try:
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), mesh_file)
|
||||
except FileNotFoundError:
|
||||
path = ""
|
||||
|
||||
reader = Application.getInstance().getMeshFileHandler().getReaderForFile(path)
|
||||
node = reader.read(path)
|
||||
|
||||
if node.getMeshData():
|
||||
self.setMeshData(node.getMeshData())
|
||||
|
||||
def render(self, renderer):
|
||||
# Avoid to render if it is not visible
|
||||
if not self.isVisible():
|
||||
return False
|
||||
|
||||
if not self._shader:
|
||||
# We now misuse the platform shader, as it actually supports textures
|
||||
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
|
||||
self._shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb()))
|
||||
# Set the opacity to 0, so that the template is in full control.
|
||||
self._shader.setUniformValue("u_opacity", 0)
|
||||
|
||||
if self.getMeshData():
|
||||
renderer.queueNode(self, shader = self._shader, transparent = True)
|
||||
return True
|
||||
161
plugins/SimulationView/PathSlider.qml
Normal file
161
plugins/SimulationView/PathSlider.qml
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
|
||||
import UM 1.0 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item {
|
||||
id: sliderRoot
|
||||
|
||||
// handle properties
|
||||
property real handleSize: 10
|
||||
property real handleRadius: handleSize / 2
|
||||
property color handleColor: "black"
|
||||
property color handleActiveColor: "white"
|
||||
property color rangeColor: "black"
|
||||
property real handleLabelWidth: width
|
||||
|
||||
// track properties
|
||||
property real trackThickness: 4 // width of the slider track
|
||||
property real trackRadius: trackThickness / 2
|
||||
property color trackColor: "white"
|
||||
property real trackBorderWidth: 1 // width of the slider track border
|
||||
property color trackBorderColor: "black"
|
||||
|
||||
// value properties
|
||||
property real maximumValue: 100
|
||||
property bool roundValues: true
|
||||
property real handleValue: maximumValue
|
||||
|
||||
property bool pathsVisible: true
|
||||
|
||||
function getHandleValueFromSliderHandle () {
|
||||
return handle.getValue()
|
||||
}
|
||||
|
||||
function setHandleValue (value) {
|
||||
handle.setValue(value)
|
||||
updateRangeHandle()
|
||||
}
|
||||
|
||||
function updateRangeHandle () {
|
||||
rangeHandle.width = handle.x - sliderRoot.handleSize
|
||||
}
|
||||
|
||||
// slider track
|
||||
Rectangle {
|
||||
id: track
|
||||
|
||||
width: sliderRoot.width - sliderRoot.handleSize
|
||||
height: sliderRoot.trackThickness
|
||||
radius: sliderRoot.trackRadius
|
||||
anchors.centerIn: sliderRoot
|
||||
color: sliderRoot.trackColor
|
||||
border.width: sliderRoot.trackBorderWidth
|
||||
border.color: sliderRoot.trackBorderColor
|
||||
visible: sliderRoot.pathsVisible
|
||||
}
|
||||
|
||||
// Progress indicator
|
||||
Item {
|
||||
id: rangeHandle
|
||||
|
||||
x: handle.width
|
||||
height: sliderRoot.handleSize
|
||||
width: handle.x - sliderRoot.handleSize
|
||||
anchors.verticalCenter: sliderRoot.verticalCenter
|
||||
visible: sliderRoot.pathsVisible
|
||||
|
||||
Rectangle {
|
||||
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
|
||||
width: parent.width + sliderRoot.handleSize
|
||||
anchors.centerIn: parent
|
||||
color: sliderRoot.rangeColor
|
||||
}
|
||||
}
|
||||
|
||||
// Handle
|
||||
Rectangle {
|
||||
id: handle
|
||||
|
||||
x: sliderRoot.handleSize
|
||||
width: sliderRoot.handleSize
|
||||
height: sliderRoot.handleSize
|
||||
anchors.verticalCenter: sliderRoot.verticalCenter
|
||||
radius: sliderRoot.handleRadius
|
||||
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
|
||||
visible: sliderRoot.pathsVisible
|
||||
|
||||
function onHandleDragged () {
|
||||
|
||||
// update the range handle
|
||||
sliderRoot.updateRangeHandle()
|
||||
|
||||
// set the new value after moving the handle position
|
||||
UM.SimulationView.setCurrentPath(getValue())
|
||||
}
|
||||
|
||||
// get the value based on the slider position
|
||||
function getValue () {
|
||||
var result = x / (sliderRoot.width - sliderRoot.handleSize)
|
||||
result = result * sliderRoot.maximumValue
|
||||
result = sliderRoot.roundValues ? Math.round(result) : result
|
||||
return result
|
||||
}
|
||||
|
||||
// set the slider position based on the value
|
||||
function setValue (value) {
|
||||
|
||||
UM.SimulationView.setCurrentPath(value)
|
||||
|
||||
var diff = value / sliderRoot.maximumValue
|
||||
var newXPosition = Math.round(diff * (sliderRoot.width - sliderRoot.handleSize))
|
||||
x = newXPosition
|
||||
|
||||
// update the range handle
|
||||
sliderRoot.updateRangeHandle()
|
||||
}
|
||||
|
||||
Keys.onRightPressed: handleLabel.setValue(handleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||
|
||||
// dragging
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
drag {
|
||||
target: parent
|
||||
axis: Drag.XAxis
|
||||
minimumX: 0
|
||||
maximumX: sliderRoot.width - sliderRoot.handleSize
|
||||
}
|
||||
onPressed: {
|
||||
handleLabel.forceActiveFocus()
|
||||
}
|
||||
|
||||
onPositionChanged: parent.onHandleDragged()
|
||||
}
|
||||
|
||||
SimulationSliderLabel {
|
||||
id: handleLabel
|
||||
|
||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
y: parent.y + sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
target: Qt.point(x + width / 2, sliderRoot.height)
|
||||
visible: false
|
||||
startFrom: 0
|
||||
|
||||
// custom properties
|
||||
maximumValue: sliderRoot.maximumValue
|
||||
value: sliderRoot.handleValue
|
||||
busy: UM.SimulationView.busy
|
||||
setValue: handle.setValue // connect callback functions
|
||||
}
|
||||
}
|
||||
}
|
||||
190
plugins/SimulationView/SimulationPass.py
Normal file
190
plugins/SimulationView/SimulationPass.py
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Math.Color import Color
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.ToolHandle import ToolHandle
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from UM.View.RenderPass import RenderPass
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
|
||||
import os.path
|
||||
|
||||
## RenderPass used to display g-code paths.
|
||||
from .NozzleNode import NozzleNode
|
||||
|
||||
|
||||
class SimulationPass(RenderPass):
|
||||
def __init__(self, width, height):
|
||||
super().__init__("simulationview", width, height)
|
||||
|
||||
self._layer_shader = None
|
||||
self._layer_shadow_shader = None
|
||||
self._current_shader = None # This shader will be the shadow or the normal depending if the user wants to see the paths or the layers
|
||||
self._tool_handle_shader = None
|
||||
self._nozzle_shader = None
|
||||
self._old_current_layer = 0
|
||||
self._old_current_path = 0
|
||||
self._switching_layers = True # It tracks when the user is moving the layers' slider
|
||||
self._gl = OpenGL.getInstance().getBindingsObject()
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._extruder_manager = ExtruderManager.getInstance()
|
||||
|
||||
self._layer_view = None
|
||||
self._compatibility_mode = None
|
||||
|
||||
def setSimulationView(self, layerview):
|
||||
self._layer_view = layerview
|
||||
self._compatibility_mode = layerview.getCompatibilityMode()
|
||||
|
||||
def render(self):
|
||||
if not self._layer_shader:
|
||||
if self._compatibility_mode:
|
||||
shader_filename = "layers.shader"
|
||||
shadow_shader_filename = "layers_shadow.shader"
|
||||
else:
|
||||
shader_filename = "layers3d.shader"
|
||||
shadow_shader_filename = "layers3d_shadow.shader"
|
||||
self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shader_filename))
|
||||
self._layer_shadow_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shadow_shader_filename))
|
||||
self._current_shader = self._layer_shader
|
||||
# Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
|
||||
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
|
||||
if self._layer_view:
|
||||
self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
|
||||
self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
|
||||
self._layer_shader.setUniformValue("u_max_thickness", self._layer_view.getMaxThickness())
|
||||
self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness())
|
||||
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType())
|
||||
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
|
||||
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
|
||||
self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers())
|
||||
self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
|
||||
self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
|
||||
else:
|
||||
#defaults
|
||||
self._layer_shader.setUniformValue("u_max_feedrate", 1)
|
||||
self._layer_shader.setUniformValue("u_min_feedrate", 0)
|
||||
self._layer_shader.setUniformValue("u_max_thickness", 1)
|
||||
self._layer_shader.setUniformValue("u_min_thickness", 0)
|
||||
self._layer_shader.setUniformValue("u_layer_view_type", 1)
|
||||
self._layer_shader.setUniformValue("u_extruder_opacity", [1, 1, 1, 1])
|
||||
self._layer_shader.setUniformValue("u_show_travel_moves", 0)
|
||||
self._layer_shader.setUniformValue("u_show_helpers", 1)
|
||||
self._layer_shader.setUniformValue("u_show_skin", 1)
|
||||
self._layer_shader.setUniformValue("u_show_infill", 1)
|
||||
|
||||
if not self._tool_handle_shader:
|
||||
self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
|
||||
|
||||
if not self._nozzle_shader:
|
||||
self._nozzle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
|
||||
self._nozzle_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb()))
|
||||
|
||||
self.bind()
|
||||
|
||||
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
|
||||
head_position = None # Indicates the current position of the print head
|
||||
nozzle_node = None
|
||||
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
|
||||
if isinstance(node, ToolHandle):
|
||||
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
|
||||
|
||||
elif isinstance(node, NozzleNode):
|
||||
nozzle_node = node
|
||||
nozzle_node.setVisible(False)
|
||||
|
||||
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
||||
# Render all layers below a certain number as line mesh instead of vertices.
|
||||
if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
|
||||
start = 0
|
||||
end = 0
|
||||
element_counts = layer_data.getElementCounts()
|
||||
for layer in sorted(element_counts.keys()):
|
||||
# In the current layer, we show just the indicated paths
|
||||
if layer == self._layer_view._current_layer_num:
|
||||
# We look for the position of the head, searching the point of the current path
|
||||
index = self._layer_view._current_path_num
|
||||
offset = 0
|
||||
for polygon in layer_data.getLayer(layer).polygons:
|
||||
# The size indicates all values in the two-dimension array, and the second dimension is
|
||||
# always size 3 because we have 3D points.
|
||||
if index >= polygon.data.size // 3 - offset:
|
||||
index -= polygon.data.size // 3 - offset
|
||||
offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
|
||||
continue
|
||||
# The head position is calculated and translated
|
||||
head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition()
|
||||
break
|
||||
break
|
||||
if self._layer_view._minimum_layer_num > layer:
|
||||
start += element_counts[layer]
|
||||
end += element_counts[layer]
|
||||
|
||||
# Calculate the range of paths in the last layer
|
||||
current_layer_start = end
|
||||
current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice
|
||||
|
||||
# This uses glDrawRangeElements internally to only draw a certain range of lines.
|
||||
# All the layers but the current selected layer are rendered first
|
||||
if self._old_current_path != self._layer_view._current_path_num:
|
||||
self._current_shader = self._layer_shadow_shader
|
||||
self._switching_layers = False
|
||||
if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num:
|
||||
self._current_shader = self._layer_shader
|
||||
self._switching_layers = True
|
||||
|
||||
layers_batch = RenderBatch(self._current_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end))
|
||||
layers_batch.addItem(node.getWorldTransformation(), layer_data)
|
||||
layers_batch.render(self._scene.getActiveCamera())
|
||||
|
||||
# Current selected layer is rendered
|
||||
current_layer_batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (current_layer_start, current_layer_end))
|
||||
current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
|
||||
current_layer_batch.render(self._scene.getActiveCamera())
|
||||
|
||||
self._old_current_layer = self._layer_view._current_layer_num
|
||||
self._old_current_path = self._layer_view._current_path_num
|
||||
|
||||
# Create a new batch that is not range-limited
|
||||
batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
|
||||
|
||||
if self._layer_view.getCurrentLayerMesh():
|
||||
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh())
|
||||
|
||||
if self._layer_view.getCurrentLayerJumps():
|
||||
batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps())
|
||||
|
||||
if len(batch.items) > 0:
|
||||
batch.render(self._scene.getActiveCamera())
|
||||
|
||||
# The nozzle is drawn when once we know the correct position of the head,
|
||||
# but the user is not using the layer slider, and the compatibility mode is not enabled
|
||||
if not self._switching_layers and not self._compatibility_mode and self._layer_view.getActivity() and nozzle_node is not None:
|
||||
if head_position is not None:
|
||||
nozzle_node.setVisible(True)
|
||||
nozzle_node.setPosition(head_position)
|
||||
nozzle_batch = RenderBatch(self._nozzle_shader, type = RenderBatch.RenderType.Transparent)
|
||||
nozzle_batch.addItem(nozzle_node.getWorldTransformation(), mesh = nozzle_node.getMeshData())
|
||||
nozzle_batch.render(self._scene.getActiveCamera())
|
||||
|
||||
# Render toolhandles on top of the layerview
|
||||
if len(tool_handle_batch.items) > 0:
|
||||
tool_handle_batch.render(self._scene.getActiveCamera())
|
||||
|
||||
self.release()
|
||||
|
|
@ -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: 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: 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
|
||||
}
|
||||
}
|
||||
219
plugins/LayerView/LayerView.py → plugins/SimulationView/SimulationView.py
Executable file → Normal file
219
plugins/LayerView/LayerView.py → plugins/SimulationView/SimulationView.py
Executable file → Normal file
|
|
@ -1,46 +1,46 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import sys
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Resources import Resources
|
||||
from UM.Event import Event, KeyEvent
|
||||
from UM.Signal import Signal
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Math.Color import Color
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Job import Job
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Logger import Logger
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.Message import Message
|
||||
from UM.Application import Application
|
||||
from UM.View.GL.OpenGLContext import OpenGLContext
|
||||
|
||||
from cura.ConvexHullNode import ConvexHullNode
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from . import LayerViewProxy
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event, KeyEvent
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Color import Color
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Message import Message
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Resources import Resources
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Signal import Signal
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.View.GL.OpenGLContext import OpenGLContext
|
||||
from UM.View.View import View
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
from cura.ConvexHullNode import ConvexHullNode
|
||||
|
||||
from . import LayerPass
|
||||
from .NozzleNode import NozzleNode
|
||||
from .SimulationPass import SimulationPass
|
||||
from .SimulationViewProxy import SimulationViewProxy
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
import numpy
|
||||
import os.path
|
||||
|
||||
## View used to display g-code paths.
|
||||
class LayerView(View):
|
||||
# Must match LayerView.qml
|
||||
class SimulationView(View):
|
||||
# Must match SimulationView.qml
|
||||
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
|
||||
LAYER_VIEW_TYPE_LINE_TYPE = 1
|
||||
LAYER_VIEW_TYPE_FEEDRATE = 2
|
||||
LAYER_VIEW_TYPE_THICKNESS = 3
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -54,22 +54,29 @@ class LayerView(View):
|
|||
self._activity = False
|
||||
self._old_max_layers = 0
|
||||
|
||||
self._max_paths = 0
|
||||
self._current_path_num = 0
|
||||
self._minimum_path_num = 0
|
||||
self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged)
|
||||
|
||||
self._busy = False
|
||||
self._simulation_running = False
|
||||
|
||||
self._ghost_shader = None
|
||||
self._layer_pass = None
|
||||
self._composite_pass = None
|
||||
self._old_layer_bindings = None
|
||||
self._layerview_composite_shader = None
|
||||
self._simulationview_composite_shader = None
|
||||
self._old_composite_shader = None
|
||||
|
||||
self._global_container_stack = None
|
||||
self._proxy = LayerViewProxy.LayerViewProxy()
|
||||
self._proxy = SimulationViewProxy()
|
||||
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
||||
|
||||
self._resetSettings()
|
||||
self._legend_items = None
|
||||
self._show_travel_moves = False
|
||||
self._nozzle_node = None
|
||||
|
||||
Preferences.getInstance().addPreference("view/top_layer_count", 5)
|
||||
Preferences.getInstance().addPreference("view/only_show_top_layers", False)
|
||||
|
|
@ -91,7 +98,7 @@ class LayerView(View):
|
|||
self._compatibility_mode = True # for safety
|
||||
|
||||
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
|
||||
title = catalog.i18nc("@info:title", "Layer View"))
|
||||
title = catalog.i18nc("@info:title", "Simulation View"))
|
||||
|
||||
def _resetSettings(self):
|
||||
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
|
||||
|
|
@ -101,18 +108,24 @@ class LayerView(View):
|
|||
self._show_helpers = 1
|
||||
self._show_skin = 1
|
||||
self._show_infill = 1
|
||||
self.resetLayerData()
|
||||
|
||||
def getActivity(self):
|
||||
return self._activity
|
||||
|
||||
def getLayerPass(self):
|
||||
def setActivity(self, activity):
|
||||
if self._activity == activity:
|
||||
return
|
||||
self._activity = activity
|
||||
self.activityChanged.emit()
|
||||
|
||||
def getSimulationPass(self):
|
||||
if not self._layer_pass:
|
||||
# Currently the RenderPass constructor requires a size > 0
|
||||
# This should be fixed in RenderPass's constructor.
|
||||
self._layer_pass = LayerPass.LayerPass(1, 1)
|
||||
self._layer_pass = SimulationPass(1, 1)
|
||||
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
|
||||
self._layer_pass.setLayerView(self)
|
||||
self.getRenderer().addRenderPass(self._layer_pass)
|
||||
self._layer_pass.setSimulationView(self)
|
||||
return self._layer_pass
|
||||
|
||||
def getCurrentLayer(self):
|
||||
|
|
@ -121,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
|
||||
|
|
@ -137,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()
|
||||
|
|
@ -187,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
|
||||
|
|
@ -244,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
|
||||
|
|
@ -256,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:
|
||||
|
|
@ -282,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.
|
||||
|
|
@ -309,25 +413,31 @@ class LayerView(View):
|
|||
return True
|
||||
|
||||
if event.type == Event.ViewActivateEvent:
|
||||
# Make sure the LayerPass is created
|
||||
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,8 @@ 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)
|
||||
|
||||
|
|
@ -363,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
|
||||
|
|
@ -396,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:
|
||||
707
plugins/SimulationView/SimulationView.qml
Normal file
707
plugins/SimulationView/SimulationView.qml
Normal file
|
|
@ -0,0 +1,707 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.4
|
||||
import QtQuick.Controls 1.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
|
||||
import UM 1.0 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
width: {
|
||||
if (UM.SimulationView.compatibilityMode) {
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
||||
} else {
|
||||
return UM.Theme.getSize("layerview_menu_size").width;
|
||||
}
|
||||
}
|
||||
height: {
|
||||
if (viewSettings.collapsed) {
|
||||
if (UM.SimulationView.compatibilityMode) {
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
|
||||
}
|
||||
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
|
||||
} else if (UM.SimulationView.compatibilityMode) {
|
||||
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
|
||||
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
|
||||
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||
} else {
|
||||
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||
}
|
||||
}
|
||||
Behavior on height { NumberAnimation { duration: 100 } }
|
||||
|
||||
property var buttonTarget: {
|
||||
if(parent != null)
|
||||
{
|
||||
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
||||
return base.mapFromItem(parent.parent, parent.buttonTarget.x, parent.buttonTarget.y)
|
||||
}
|
||||
return Qt.point(0,0)
|
||||
}
|
||||
|
||||
visible: parent != null ? !parent.parent.monitoringPrint: true
|
||||
|
||||
Rectangle {
|
||||
id: layerViewMenu
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
clip: true
|
||||
z: layerSlider.z - 1
|
||||
color: UM.Theme.getColor("tool_panel_background")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
|
||||
Button {
|
||||
id: collapseButton
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Math.floor(UM.Theme.getSize("default_margin").height + (UM.Theme.getSize("layerview_row").height - UM.Theme.getSize("default_margin").height) / 2)
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
width: UM.Theme.getSize("standard_arrow").width
|
||||
height: UM.Theme.getSize("standard_arrow").height
|
||||
|
||||
onClicked: viewSettings.collapsed = !viewSettings.collapsed
|
||||
|
||||
style: ButtonStyle
|
||||
{
|
||||
background: UM.RecolorImage
|
||||
{
|
||||
width: control.width
|
||||
height: control.height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
source: viewSettings.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
|
||||
}
|
||||
label: Label{ }
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: viewSettings
|
||||
|
||||
property bool collapsed: false
|
||||
property var extruder_opacities: UM.Preferences.getValue("layerview/extruder_opacities").split("|")
|
||||
property bool show_travel_moves: UM.Preferences.getValue("layerview/show_travel_moves")
|
||||
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
|
||||
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
|
||||
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
|
||||
// if we are in compatibility mode, we only show the "line type"
|
||||
property bool show_legend: UM.SimulationView.compatibilityMode ? true : UM.Preferences.getValue("layerview/layer_view_type") == 1
|
||||
property bool show_gradient: UM.SimulationView.compatibilityMode ? false : UM.Preferences.getValue("layerview/layer_view_type") == 2 || UM.Preferences.getValue("layerview/layer_view_type") == 3
|
||||
property bool show_feedrate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 2
|
||||
property bool show_thickness_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 3
|
||||
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
|
||||
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
spacing: UM.Theme.getSize("layerview_row_spacing").height
|
||||
|
||||
Label
|
||||
{
|
||||
id: layerViewTypesLabel
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@label","Color scheme")
|
||||
font: UM.Theme.getFont("default");
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
Layout.fillWidth: true
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
}
|
||||
|
||||
ListModel // matches SimulationView.py
|
||||
{
|
||||
id: layerViewTypes
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
layerViewTypes.append({
|
||||
text: catalog.i18nc("@label:listbox", "Material Color"),
|
||||
type_id: 0
|
||||
})
|
||||
layerViewTypes.append({
|
||||
text: catalog.i18nc("@label:listbox", "Line Type"),
|
||||
type_id: 1
|
||||
})
|
||||
layerViewTypes.append({
|
||||
text: catalog.i18nc("@label:listbox", "Feedrate"),
|
||||
type_id: 2
|
||||
})
|
||||
layerViewTypes.append({
|
||||
text: catalog.i18nc("@label:listbox", "Layer thickness"),
|
||||
type_id: 3 // these ids match the switching in the shader
|
||||
})
|
||||
}
|
||||
|
||||
ComboBox
|
||||
{
|
||||
id: layerTypeCombobox
|
||||
anchors.left: parent.left
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
model: layerViewTypes
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
style: UM.Theme.styles.combobox
|
||||
anchors.right: parent.right
|
||||
|
||||
onActivated:
|
||||
{
|
||||
UM.Preferences.setValue("layerview/layer_view_type", index);
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
|
||||
updateLegends(currentIndex);
|
||||
}
|
||||
|
||||
function updateLegends(type_id)
|
||||
{
|
||||
// update visibility of legends
|
||||
viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
|
||||
viewSettings.show_gradient = !UM.SimulationView.compatibilityMode && (type_id == 2 || type_id == 3);
|
||||
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
|
||||
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: compatibilityModeLabel
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@label","Compatibility Mode")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
visible: UM.SimulationView.compatibilityMode
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
height: Math.floor(UM.Theme.getSize("default_margin").width / 2)
|
||||
width: width
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: UM.Preferences
|
||||
onPreferenceChanged:
|
||||
{
|
||||
layerTypeCombobox.currentIndex = UM.SimulationView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
|
||||
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
|
||||
playButton.pauseSimulation();
|
||||
viewSettings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
|
||||
viewSettings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
|
||||
viewSettings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
|
||||
viewSettings.show_skin = UM.Preferences.getValue("layerview/show_skin");
|
||||
viewSettings.show_infill = UM.Preferences.getValue("layerview/show_infill");
|
||||
viewSettings.only_show_top_layers = UM.Preferences.getValue("view/only_show_top_layers");
|
||||
viewSettings.top_layer_count = UM.Preferences.getValue("view/top_layer_count");
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Cura.ExtrudersModel{}
|
||||
CheckBox {
|
||||
id: extrudersModelCheckBox
|
||||
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
|
||||
onClicked: {
|
||||
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
||||
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
|
||||
}
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
enabled: index + 1 <= 4
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: extrudersModelCheckBox.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
color: model.color
|
||||
radius: width / 2
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: !viewSettings.show_legend & !viewSettings.show_gradient
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
style: UM.Theme.styles.checkbox
|
||||
Label
|
||||
{
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: extrudersModelCheckBox.left;
|
||||
anchors.right: extrudersModelCheckBox.right;
|
||||
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
id: typesLegendModel
|
||||
Component.onCompleted:
|
||||
{
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Show Travels"),
|
||||
initialValue: viewSettings.show_travel_moves,
|
||||
preference: "layerview/show_travel_moves",
|
||||
colorId: "layerview_move_combing"
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Show Helpers"),
|
||||
initialValue: viewSettings.show_helpers,
|
||||
preference: "layerview/show_helpers",
|
||||
colorId: "layerview_support"
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Show Shell"),
|
||||
initialValue: viewSettings.show_skin,
|
||||
preference: "layerview/show_skin",
|
||||
colorId: "layerview_inset_0"
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Show Infill"),
|
||||
initialValue: viewSettings.show_infill,
|
||||
preference: "layerview/show_infill",
|
||||
colorId: "layerview_infill"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: legendModelCheckBox
|
||||
checked: model.initialValue
|
||||
onClicked: {
|
||||
UM.Preferences.setValue(model.preference, checked);
|
||||
}
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: legendModelCheckBox.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
color: UM.Theme.getColor(model.colorId)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: viewSettings.show_legend
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
style: UM.Theme.styles.checkbox
|
||||
Label
|
||||
{
|
||||
text: label
|
||||
font: UM.Theme.getFont("default")
|
||||
elide: Text.ElideRight
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: legendModelCheckBox.left;
|
||||
anchors.right: legendModelCheckBox.right;
|
||||
anchors.leftMargin: UM.Theme.getSize("checkbox").width + UM.Theme.getSize("default_margin").width /2
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
checked: viewSettings.only_show_top_layers
|
||||
onClicked: {
|
||||
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
|
||||
}
|
||||
text: catalog.i18nc("@label", "Only Show Top Layers")
|
||||
visible: UM.SimulationView.compatibilityMode
|
||||
style: UM.Theme.styles.checkbox
|
||||
}
|
||||
CheckBox {
|
||||
checked: viewSettings.top_layer_count == 5
|
||||
onClicked: {
|
||||
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
|
||||
}
|
||||
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
||||
visible: UM.SimulationView.compatibilityMode
|
||||
style: UM.Theme.styles.checkbox
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
id: typesLegendModelNoCheck
|
||||
Component.onCompleted:
|
||||
{
|
||||
typesLegendModelNoCheck.append({
|
||||
label: catalog.i18nc("@label", "Top / Bottom"),
|
||||
colorId: "layerview_skin",
|
||||
});
|
||||
typesLegendModelNoCheck.append({
|
||||
label: catalog.i18nc("@label", "Inner Wall"),
|
||||
colorId: "layerview_inset_x",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: label
|
||||
visible: viewSettings.show_legend
|
||||
id: typesLegendModelLabel
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: typesLegendModelLabel.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
color: UM.Theme.getColor(model.colorId)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: viewSettings.show_legend
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
|
||||
// Text for the minimum, maximum and units for the feedrates and layer thickness
|
||||
Item {
|
||||
id: gradientLegend
|
||||
visible: viewSettings.show_gradient
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("layerview_row").height
|
||||
anchors {
|
||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: minText()
|
||||
anchors.left: parent.left
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
|
||||
function minText() {
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
||||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
||||
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
||||
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
|
||||
}
|
||||
}
|
||||
return catalog.i18nc("@label","min")
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: unitsText()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
|
||||
function unitsText() {
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
||||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
||||
return "mm/s"
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
||||
return "mm"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: maxText()
|
||||
anchors.right: parent.right
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
|
||||
function maxText() {
|
||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
||||
// Feedrate selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
||||
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
|
||||
}
|
||||
// Layer thickness selected
|
||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
||||
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
|
||||
}
|
||||
}
|
||||
return catalog.i18nc("@label","max")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gradient colors for feedrate
|
||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
||||
// Invert values because then the bar is rotated 90 degrees
|
||||
id: feedrateGradient
|
||||
visible: viewSettings.show_feedrate_gradient
|
||||
anchors.left: parent.right
|
||||
height: parent.width
|
||||
width: UM.Theme.getSize("layerview_row").height * 1.5
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.000
|
||||
color: Qt.rgba(1, 0.5, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.625
|
||||
color: Qt.rgba(0.375, 0.5, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.75
|
||||
color: Qt.rgba(0.25, 1, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.rgba(0, 0, 1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gradient colors for layer thickness
|
||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
||||
// Invert values because then the bar is rotated 90 degrees
|
||||
id: thicknessGradient
|
||||
visible: viewSettings.show_thickness_gradient
|
||||
anchors.left: parent.right
|
||||
height: parent.width
|
||||
width: UM.Theme.getSize("layerview_row").height * 1.5
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.000
|
||||
color: Qt.rgba(1, 0, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.25
|
||||
color: Qt.rgba(0.5, 0.5, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.5
|
||||
color: Qt.rgba(0, 1, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.75
|
||||
color: Qt.rgba(0, 0.5, 0.5, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.rgba(0, 0, 1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: slidersBox
|
||||
|
||||
width: parent.width
|
||||
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
||||
|
||||
anchors {
|
||||
top: parent.bottom
|
||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||
left: parent.left
|
||||
}
|
||||
|
||||
PathSlider {
|
||||
id: pathSlider
|
||||
|
||||
height: UM.Theme.getSize("slider_handle").width
|
||||
anchors.left: playButton.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
|
||||
// custom properties
|
||||
handleValue: UM.SimulationView.currentPath
|
||||
maximumValue: UM.SimulationView.numPaths
|
||||
handleSize: UM.Theme.getSize("slider_handle").width
|
||||
trackThickness: UM.Theme.getSize("slider_groove").width
|
||||
trackColor: UM.Theme.getColor("slider_groove")
|
||||
trackBorderColor: UM.Theme.getColor("slider_groove_border")
|
||||
handleColor: UM.Theme.getColor("slider_handle")
|
||||
handleActiveColor: UM.Theme.getColor("slider_handle_active")
|
||||
rangeColor: UM.Theme.getColor("slider_groove_fill")
|
||||
|
||||
// update values when layer data changes
|
||||
Connections {
|
||||
target: UM.SimulationView
|
||||
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||
}
|
||||
|
||||
// make sure the slider handlers show the correct value after switching views
|
||||
Component.onCompleted: {
|
||||
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||
}
|
||||
}
|
||||
|
||||
LayerSlider {
|
||||
id: layerSlider
|
||||
|
||||
width: UM.Theme.getSize("slider_handle").width
|
||||
height: UM.Theme.getSize("layerview_menu_size").height
|
||||
|
||||
anchors {
|
||||
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
|
||||
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
|
||||
right: parent.right
|
||||
rightMargin: UM.Theme.getSize("slider_layerview_margin").width
|
||||
}
|
||||
|
||||
// custom properties
|
||||
upperValue: UM.SimulationView.currentLayer
|
||||
lowerValue: UM.SimulationView.minimumLayer
|
||||
maximumValue: UM.SimulationView.numLayers
|
||||
handleSize: UM.Theme.getSize("slider_handle").width
|
||||
trackThickness: UM.Theme.getSize("slider_groove").width
|
||||
trackColor: UM.Theme.getColor("slider_groove")
|
||||
trackBorderColor: UM.Theme.getColor("slider_groove_border")
|
||||
upperHandleColor: UM.Theme.getColor("slider_handle")
|
||||
lowerHandleColor: UM.Theme.getColor("slider_handle")
|
||||
rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
|
||||
handleActiveColor: UM.Theme.getColor("slider_handle_active")
|
||||
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
|
||||
|
||||
// update values when layer data changes
|
||||
Connections {
|
||||
target: UM.SimulationView
|
||||
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||
onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
}
|
||||
|
||||
// make sure the slider handlers show the correct value after switching views
|
||||
Component.onCompleted: {
|
||||
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
}
|
||||
}
|
||||
|
||||
// Play simulation button
|
||||
Button {
|
||||
id: playButton
|
||||
iconSource: "./resources/simulation_resume.svg"
|
||||
style: UM.Theme.styles.small_tool_button
|
||||
visible: !UM.SimulationView.compatibilityMode
|
||||
anchors {
|
||||
verticalCenter: pathSlider.verticalCenter
|
||||
}
|
||||
|
||||
property var status: 0 // indicates if it's stopped (0) or playing (1)
|
||||
|
||||
onClicked: {
|
||||
switch(status) {
|
||||
case 0: {
|
||||
resumeSimulation()
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
pauseSimulation()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pauseSimulation() {
|
||||
UM.SimulationView.setSimulationRunning(false)
|
||||
iconSource = "./resources/simulation_resume.svg"
|
||||
simulationTimer.stop()
|
||||
status = 0
|
||||
}
|
||||
|
||||
function resumeSimulation() {
|
||||
UM.SimulationView.setSimulationRunning(true)
|
||||
iconSource = "./resources/simulation_pause.svg"
|
||||
simulationTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
Timer
|
||||
{
|
||||
id: simulationTimer
|
||||
interval: 100
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var currentPath = UM.SimulationView.currentPath
|
||||
var numPaths = UM.SimulationView.numPaths
|
||||
var currentLayer = UM.SimulationView.currentLayer
|
||||
var numLayers = UM.SimulationView.numLayers
|
||||
// When the user plays the simulation, if the path slider is at the end of this layer, we start
|
||||
// the simulation at the beginning of the current layer.
|
||||
if (playButton.status == 0)
|
||||
{
|
||||
if (currentPath >= numPaths)
|
||||
{
|
||||
UM.SimulationView.setCurrentPath(0)
|
||||
}
|
||||
else
|
||||
{
|
||||
UM.SimulationView.setCurrentPath(currentPath+1)
|
||||
}
|
||||
}
|
||||
// If the simulation is already playing and we reach the end of a layer, then it automatically
|
||||
// starts at the beginning of the next layer.
|
||||
else
|
||||
{
|
||||
if (currentPath >= numPaths)
|
||||
{
|
||||
// At the end of the model, the simulation stops
|
||||
if (currentLayer >= numLayers)
|
||||
{
|
||||
playButton.pauseSimulation()
|
||||
}
|
||||
else
|
||||
{
|
||||
UM.SimulationView.setCurrentLayer(currentLayer+1)
|
||||
UM.SimulationView.setCurrentPath(0)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UM.SimulationView.setCurrentPath(currentPath+1)
|
||||
}
|
||||
}
|
||||
playButton.status = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
259
plugins/SimulationView/SimulationViewProxy.py
Normal file
259
plugins/SimulationView/SimulationViewProxy.py
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Application import Application
|
||||
|
||||
import SimulationView
|
||||
|
||||
|
||||
class SimulationViewProxy(QObject):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._current_layer = 0
|
||||
self._controller = Application.getInstance().getController()
|
||||
self._controller.activeViewChanged.connect(self._onActiveViewChanged)
|
||||
self._onActiveViewChanged()
|
||||
self.is_simulationView_selected = False
|
||||
|
||||
currentLayerChanged = pyqtSignal()
|
||||
currentPathChanged = pyqtSignal()
|
||||
maxLayersChanged = pyqtSignal()
|
||||
maxPathsChanged = pyqtSignal()
|
||||
activityChanged = pyqtSignal()
|
||||
globalStackChanged = pyqtSignal()
|
||||
preferencesChanged = pyqtSignal()
|
||||
busyChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify=activityChanged)
|
||||
def layerActivity(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getActivity()
|
||||
return False
|
||||
|
||||
@pyqtProperty(int, notify=maxLayersChanged)
|
||||
def numLayers(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMaxLayers()
|
||||
return 0
|
||||
|
||||
@pyqtProperty(int, notify=currentLayerChanged)
|
||||
def currentLayer(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getCurrentLayer()
|
||||
return 0
|
||||
|
||||
@pyqtProperty(int, notify=currentLayerChanged)
|
||||
def minimumLayer(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMinimumLayer()
|
||||
return 0
|
||||
|
||||
@pyqtProperty(int, notify=maxPathsChanged)
|
||||
def numPaths(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMaxPaths()
|
||||
return 0
|
||||
|
||||
@pyqtProperty(int, notify=currentPathChanged)
|
||||
def currentPath(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getCurrentPath()
|
||||
return 0
|
||||
|
||||
@pyqtProperty(int, notify=currentPathChanged)
|
||||
def minimumPath(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMinimumPath()
|
||||
return 0
|
||||
|
||||
@pyqtProperty(bool, notify=busyChanged)
|
||||
def busy(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.isBusy()
|
||||
return False
|
||||
|
||||
@pyqtProperty(bool, notify=preferencesChanged)
|
||||
def compatibilityMode(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getCompatibilityMode()
|
||||
return False
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setCurrentLayer(self, layer_num):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setLayer(layer_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setMinimumLayer(self, layer_num):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setMinimumLayer(layer_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setCurrentPath(self, path_num):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setPath(path_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setMinimumPath(self, path_num):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setMinimumPath(path_num)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setSimulationViewType(self, layer_view_type):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setSimulationViewisinstance(layer_view_type)
|
||||
|
||||
@pyqtSlot(result=int)
|
||||
def getSimulationViewType(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getSimulationViewType()
|
||||
return 0
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def setSimulationRunning(self, running):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setSimulationRunning(running)
|
||||
|
||||
@pyqtSlot(result=bool)
|
||||
def getSimulationRunning(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.isSimulationRunning()
|
||||
return False
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMinFeedrate(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMinFeedrate()
|
||||
return 0
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMaxFeedrate(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMaxFeedrate()
|
||||
return 0
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMinThickness(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMinThickness()
|
||||
return 0
|
||||
|
||||
@pyqtSlot(result=float)
|
||||
def getMaxThickness(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getMaxThickness()
|
||||
return 0
|
||||
|
||||
# Opacity 0..1
|
||||
@pyqtSlot(int, float)
|
||||
def setExtruderOpacity(self, extruder_nr, opacity):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setExtruderOpacity(extruder_nr, opacity)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowTravelMoves(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setShowTravelMoves(show)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowHelpers(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setShowHelpers(show)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowSkin(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setShowSkin(show)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setShowInfill(self, show):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
active_view.setShowInfill(show)
|
||||
|
||||
@pyqtProperty(int, notify=globalStackChanged)
|
||||
def extruderCount(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
return active_view.getExtruderCount()
|
||||
return 0
|
||||
|
||||
def _layerActivityChanged(self):
|
||||
self.activityChanged.emit()
|
||||
|
||||
def _onLayerChanged(self):
|
||||
self.currentLayerChanged.emit()
|
||||
self._layerActivityChanged()
|
||||
|
||||
def _onPathChanged(self):
|
||||
self.currentPathChanged.emit()
|
||||
self._layerActivityChanged()
|
||||
|
||||
def _onMaxLayersChanged(self):
|
||||
self.maxLayersChanged.emit()
|
||||
|
||||
def _onMaxPathsChanged(self):
|
||||
self.maxPathsChanged.emit()
|
||||
|
||||
def _onBusyChanged(self):
|
||||
self.busyChanged.emit()
|
||||
|
||||
def _onActivityChanged(self):
|
||||
self.activityChanged.emit()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
self.globalStackChanged.emit()
|
||||
|
||||
def _onPreferencesChanged(self):
|
||||
self.preferencesChanged.emit()
|
||||
|
||||
def _onActiveViewChanged(self):
|
||||
active_view = self._controller.getActiveView()
|
||||
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
|
||||
# remove other connection if once the SimulationView was created.
|
||||
if self.is_simulationView_selected:
|
||||
active_view.currentLayerNumChanged.disconnect(self._onLayerChanged)
|
||||
active_view.currentPathNumChanged.disconnect(self._onPathChanged)
|
||||
active_view.maxLayersChanged.disconnect(self._onMaxLayersChanged)
|
||||
active_view.maxPathsChanged.disconnect(self._onMaxPathsChanged)
|
||||
active_view.busyChanged.disconnect(self._onBusyChanged)
|
||||
active_view.activityChanged.disconnect(self._onActivityChanged)
|
||||
active_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
|
||||
active_view.preferencesChanged.disconnect(self._onPreferencesChanged)
|
||||
|
||||
self.is_simulationView_selected = True
|
||||
active_view.currentLayerNumChanged.connect(self._onLayerChanged)
|
||||
active_view.currentPathNumChanged.connect(self._onPathChanged)
|
||||
active_view.maxLayersChanged.connect(self._onMaxLayersChanged)
|
||||
active_view.maxPathsChanged.connect(self._onMaxPathsChanged)
|
||||
active_view.busyChanged.connect(self._onBusyChanged)
|
||||
active_view.activityChanged.connect(self._onActivityChanged)
|
||||
active_view.globalStackChanged.connect(self._onGlobalStackChanged)
|
||||
active_view.preferencesChanged.connect(self._onPreferencesChanged)
|
||||
26
plugins/SimulationView/__init__.py
Normal file
26
plugins/SimulationView/__init__.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtQml import qmlRegisterSingletonType
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from . import SimulationViewProxy, SimulationView
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"view": {
|
||||
"name": catalog.i18nc("@item:inlistbox", "Layer view"),
|
||||
"view_panel": "SimulationView.qml",
|
||||
"weight": 2
|
||||
}
|
||||
}
|
||||
|
||||
def createSimulationViewProxy(engine, script_engine):
|
||||
return SimulationViewProxy.SimulatorViewProxy()
|
||||
|
||||
def register(app):
|
||||
simulation_view = SimulationView.SimulationView()
|
||||
qmlRegisterSingletonType(SimulationViewProxy.SimulationViewProxy, "UM", 1, 0, "SimulationView", simulation_view.getProxy)
|
||||
return { "view": SimulationView.SimulationView()}
|
||||
0
plugins/LayerView/layers.shader → plugins/SimulationView/layers.shader
Executable file → Normal file
0
plugins/LayerView/layers.shader → plugins/SimulationView/layers.shader
Executable file → Normal file
42
plugins/LayerView/layers3d.shader → plugins/SimulationView/layers3d.shader
Executable file → Normal file
42
plugins/LayerView/layers3d.shader → plugins/SimulationView/layers3d.shader
Executable file → Normal file
|
|
@ -6,6 +6,10 @@ vertex41core =
|
|||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewProjectionMatrix;
|
||||
uniform lowp float u_active_extruder;
|
||||
uniform lowp float u_max_feedrate;
|
||||
uniform lowp float u_min_feedrate;
|
||||
uniform lowp float u_max_thickness;
|
||||
uniform lowp float u_min_thickness;
|
||||
uniform lowp int u_layer_view_type;
|
||||
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
|
||||
|
||||
|
|
@ -18,6 +22,8 @@ vertex41core =
|
|||
in highp vec2 a_line_dim; // line width and thickness
|
||||
in highp float a_extruder;
|
||||
in highp float a_line_type;
|
||||
in highp float a_feedrate;
|
||||
in highp float a_thickness;
|
||||
|
||||
out lowp vec4 v_color;
|
||||
|
||||
|
|
@ -32,6 +38,28 @@ vertex41core =
|
|||
out highp vec3 f_vertex;
|
||||
out highp vec3 f_normal;
|
||||
|
||||
vec4 feedrateGradientColor(float abs_value, float min_value, float max_value)
|
||||
{
|
||||
float value = (abs_value - min_value)/(max_value - min_value);
|
||||
float red = value;
|
||||
float green = 1-abs(1-4*value);
|
||||
if (value > 0.375)
|
||||
{
|
||||
green = 0.5;
|
||||
}
|
||||
float blue = max(1-4*value, 0);
|
||||
return vec4(red, green, blue, 1.0);
|
||||
}
|
||||
|
||||
vec4 layerThicknessGradientColor(float abs_value, float min_value, float max_value)
|
||||
{
|
||||
float value = (abs_value - min_value)/(max_value - min_value);
|
||||
float red = max(2*value-1, 0);
|
||||
float green = 1-abs(1-2*value);
|
||||
float blue = max(1-2*value, 0);
|
||||
return vec4(red, green, blue, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 v1_vertex = a_vertex;
|
||||
|
|
@ -48,6 +76,12 @@ vertex41core =
|
|||
case 1: // "Line type"
|
||||
v_color = a_color;
|
||||
break;
|
||||
case 2: // "Feedrate"
|
||||
v_color = feedrateGradientColor(a_feedrate, u_min_feedrate, u_max_feedrate);
|
||||
break;
|
||||
case 3: // "Layer thickness"
|
||||
v_color = layerThicknessGradientColor(a_line_dim.y, u_min_thickness, u_max_thickness);
|
||||
break;
|
||||
}
|
||||
|
||||
v_vertex = world_space_vert.xyz;
|
||||
|
|
@ -247,6 +281,12 @@ u_show_helpers = 1
|
|||
u_show_skin = 1
|
||||
u_show_infill = 1
|
||||
|
||||
u_min_feedrate = 0
|
||||
u_max_feedrate = 1
|
||||
|
||||
u_min_thickness = 0
|
||||
u_max_thickness = 1
|
||||
|
||||
[bindings]
|
||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
||||
u_modelMatrix = model_matrix
|
||||
|
|
@ -262,3 +302,5 @@ a_line_dim = line_dim
|
|||
a_extruder = extruder
|
||||
a_material_color = material_color
|
||||
a_line_type = line_type
|
||||
a_feedrate = feedrate
|
||||
a_thickness = thickness
|
||||
256
plugins/SimulationView/layers3d_shadow.shader
Normal file
256
plugins/SimulationView/layers3d_shadow.shader
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
[shaders]
|
||||
vertex41core =
|
||||
#version 410
|
||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
||||
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewProjectionMatrix;
|
||||
uniform lowp float u_active_extruder;
|
||||
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
|
||||
|
||||
uniform highp mat4 u_normalMatrix;
|
||||
|
||||
in highp vec4 a_vertex;
|
||||
in lowp vec4 a_color;
|
||||
in lowp vec4 a_grayColor;
|
||||
in lowp vec4 a_material_color;
|
||||
in highp vec4 a_normal;
|
||||
in highp vec2 a_line_dim; // line width and thickness
|
||||
in highp float a_extruder;
|
||||
in highp float a_line_type;
|
||||
|
||||
out lowp vec4 v_color;
|
||||
|
||||
out highp vec3 v_vertex;
|
||||
out highp vec3 v_normal;
|
||||
out lowp vec2 v_line_dim;
|
||||
out highp int v_extruder;
|
||||
out highp vec4 v_extruder_opacity;
|
||||
out float v_line_type;
|
||||
|
||||
out lowp vec4 f_color;
|
||||
out highp vec3 f_vertex;
|
||||
out highp vec3 f_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 v1_vertex = a_vertex;
|
||||
v1_vertex.y -= a_line_dim.y / 2; // half layer down
|
||||
|
||||
vec4 world_space_vert = u_modelMatrix * v1_vertex;
|
||||
gl_Position = world_space_vert;
|
||||
// shade the color depending on the extruder index stored in the alpha component of the color
|
||||
|
||||
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
|
||||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
v_line_dim = a_line_dim;
|
||||
v_extruder = int(a_extruder);
|
||||
v_line_type = a_line_type;
|
||||
v_extruder_opacity = u_extruder_opacity;
|
||||
|
||||
// for testing without geometry shader
|
||||
f_color = v_color;
|
||||
f_vertex = v_vertex;
|
||||
f_normal = v_normal;
|
||||
}
|
||||
|
||||
geometry41core =
|
||||
#version 410
|
||||
|
||||
uniform highp mat4 u_viewProjectionMatrix;
|
||||
uniform int u_show_travel_moves;
|
||||
uniform int u_show_helpers;
|
||||
uniform int u_show_skin;
|
||||
uniform int u_show_infill;
|
||||
|
||||
layout(lines) in;
|
||||
layout(triangle_strip, max_vertices = 26) out;
|
||||
|
||||
in vec4 v_color[];
|
||||
in vec3 v_vertex[];
|
||||
in vec3 v_normal[];
|
||||
in vec2 v_line_dim[];
|
||||
in int v_extruder[];
|
||||
in vec4 v_extruder_opacity[];
|
||||
in float v_line_type[];
|
||||
|
||||
out vec4 f_color;
|
||||
out vec3 f_normal;
|
||||
out vec3 f_vertex;
|
||||
|
||||
// Set the set of variables and EmitVertex
|
||||
void myEmitVertex(vec3 vertex, vec4 color, vec3 normal, vec4 pos) {
|
||||
f_vertex = vertex;
|
||||
f_color = color;
|
||||
f_normal = normal;
|
||||
gl_Position = pos;
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
||||
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
||||
vec3 g_vertex_normal_vert;
|
||||
vec4 g_vertex_offset_vert;
|
||||
vec3 g_vertex_normal_horz_head;
|
||||
vec4 g_vertex_offset_horz_head;
|
||||
|
||||
float size_x;
|
||||
float size_y;
|
||||
|
||||
if ((v_extruder_opacity[0][v_extruder[0]] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) {
|
||||
return;
|
||||
}
|
||||
// See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_skin == 0) && ((v_line_type[0] == 1) || (v_line_type[0] == 2) || (v_line_type[0] == 3))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_infill == 0) && (v_line_type[0] == 6)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
// fixed size for movements
|
||||
size_x = 0.05;
|
||||
} else {
|
||||
size_x = v_line_dim[1].x / 2 + 0.01; // radius, and make it nicely overlapping
|
||||
}
|
||||
size_y = v_line_dim[1].y / 2 + 0.01;
|
||||
|
||||
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position;
|
||||
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z));
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0);
|
||||
|
||||
g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x));
|
||||
|
||||
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz;
|
||||
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0);
|
||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
// Travels: flat plane with pointy ends
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
|
||||
|
||||
EndPrimitive();
|
||||
} else {
|
||||
// All normal lines are rendered as 3d tubes.
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
// left side
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
// right side
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head));
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
|
||||
|
||||
EndPrimitive();
|
||||
}
|
||||
}
|
||||
|
||||
fragment41core =
|
||||
#version 410
|
||||
in lowp vec4 f_color;
|
||||
in lowp vec3 f_normal;
|
||||
in lowp vec3 f_vertex;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 finalColor = vec4(0.0);
|
||||
float alpha = f_color.a;
|
||||
|
||||
finalColor.rgb += f_color.rgb * 0.3;
|
||||
|
||||
highp vec3 normal = normalize(f_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - f_vertex);
|
||||
|
||||
// Diffuse Component
|
||||
highp float NdotL = clamp(dot(normal, light_dir), 0.0, 1.0);
|
||||
finalColor += (NdotL * f_color);
|
||||
finalColor.a = alpha; // Do not change alpha in any way
|
||||
|
||||
frag_color = finalColor;
|
||||
}
|
||||
|
||||
|
||||
[defaults]
|
||||
u_active_extruder = 0.0
|
||||
u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
|
||||
|
||||
u_specularColor = [0.4, 0.4, 0.4, 1.0]
|
||||
u_ambientColor = [0.3, 0.3, 0.3, 0.0]
|
||||
u_diffuseColor = [1.0, 0.79, 0.14, 1.0]
|
||||
u_shininess = 20.0
|
||||
|
||||
u_show_travel_moves = 0
|
||||
u_show_helpers = 1
|
||||
u_show_skin = 1
|
||||
u_show_infill = 1
|
||||
|
||||
[bindings]
|
||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
||||
u_modelMatrix = model_matrix
|
||||
u_viewProjectionMatrix = view_projection_matrix
|
||||
u_normalMatrix = normal_matrix
|
||||
u_lightPosition = light_0_position
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_color = color
|
||||
a_grayColor = vec4(0.87, 0.12, 0.45, 1.0)
|
||||
a_normal = normal
|
||||
a_line_dim = line_dim
|
||||
a_extruder = extruder
|
||||
a_material_color = material_color
|
||||
a_line_type = line_type
|
||||
156
plugins/SimulationView/layers_shadow.shader
Normal file
156
plugins/SimulationView/layers_shadow.shader
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
[shaders]
|
||||
vertex =
|
||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
||||
uniform lowp float u_active_extruder;
|
||||
uniform lowp float u_shade_factor;
|
||||
uniform highp int u_layer_view_type;
|
||||
|
||||
attribute highp float a_extruder;
|
||||
attribute highp float a_line_type;
|
||||
attribute highp vec4 a_vertex;
|
||||
attribute lowp vec4 a_color;
|
||||
attribute lowp vec4 a_material_color;
|
||||
|
||||
varying lowp vec4 v_color;
|
||||
varying float v_line_type;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
||||
// shade the color depending on the extruder index
|
||||
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer;
|
||||
// 8 and 9 are travel moves
|
||||
// if ((a_line_type != 8.0) && (a_line_type != 9.0)) {
|
||||
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||
// }
|
||||
|
||||
v_line_type = a_line_type;
|
||||
}
|
||||
|
||||
fragment =
|
||||
varying lowp vec4 v_color;
|
||||
varying float v_line_type;
|
||||
|
||||
uniform int u_show_travel_moves;
|
||||
uniform int u_show_helpers;
|
||||
uniform int u_show_skin;
|
||||
uniform int u_show_infill;
|
||||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
// support: 4, 5, 7, 10
|
||||
if ((u_show_helpers == 0) && (
|
||||
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
|
||||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
|
||||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
|
||||
)) {
|
||||
discard;
|
||||
}
|
||||
// skin: 1, 2, 3
|
||||
if ((u_show_skin == 0) && (
|
||||
(v_line_type >= 0.5) && (v_line_type <= 3.5)
|
||||
)) {
|
||||
discard;
|
||||
}
|
||||
// infill:
|
||||
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
||||
gl_FragColor = v_color;
|
||||
}
|
||||
|
||||
vertex41core =
|
||||
#version 410
|
||||
uniform highp mat4 u_modelViewProjectionMatrix;
|
||||
uniform lowp float u_active_extruder;
|
||||
uniform lowp float u_shade_factor;
|
||||
uniform highp int u_layer_view_type;
|
||||
|
||||
in highp float a_extruder;
|
||||
in highp float a_line_type;
|
||||
in highp vec4 a_vertex;
|
||||
in lowp vec4 a_color;
|
||||
in lowp vec4 a_material_color;
|
||||
|
||||
out lowp vec4 v_color;
|
||||
out float v_line_type;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_modelViewProjectionMatrix * a_vertex;
|
||||
v_color = vec4(0.4, 0.4, 0.4, 0.9); // default color for not current layer
|
||||
// if ((a_line_type != 8) && (a_line_type != 9)) {
|
||||
// v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||
// }
|
||||
|
||||
v_line_type = a_line_type;
|
||||
}
|
||||
|
||||
fragment41core =
|
||||
#version 410
|
||||
in lowp vec4 v_color;
|
||||
in float v_line_type;
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform int u_show_travel_moves;
|
||||
uniform int u_show_helpers;
|
||||
uniform int u_show_skin;
|
||||
uniform int u_show_infill;
|
||||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
// helpers: 4, 5, 7, 10
|
||||
if ((u_show_helpers == 0) && (
|
||||
((v_line_type >= 3.5) && (v_line_type <= 4.5)) ||
|
||||
((v_line_type >= 6.5) && (v_line_type <= 7.5)) ||
|
||||
((v_line_type >= 9.5) && (v_line_type <= 10.5)) ||
|
||||
((v_line_type >= 4.5) && (v_line_type <= 5.5))
|
||||
)) {
|
||||
discard;
|
||||
}
|
||||
// skin: 1, 2, 3
|
||||
if ((u_show_skin == 0) && (
|
||||
(v_line_type >= 0.5) && (v_line_type <= 3.5)
|
||||
)) {
|
||||
discard;
|
||||
}
|
||||
// infill:
|
||||
if ((u_show_infill == 0) && (v_line_type >= 5.5) && (v_line_type <= 6.5)) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
||||
frag_color = v_color;
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_active_extruder = 0.0
|
||||
u_shade_factor = 0.60
|
||||
u_layer_view_type = 0
|
||||
u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
|
||||
|
||||
u_show_travel_moves = 0
|
||||
u_show_helpers = 1
|
||||
u_show_skin = 1
|
||||
u_show_infill = 1
|
||||
|
||||
[bindings]
|
||||
u_modelViewProjectionMatrix = model_view_projection_matrix
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_color = color
|
||||
a_extruder = extruder
|
||||
a_line_type = line_type
|
||||
a_material_color = material_color
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "Layer View",
|
||||
"name": "Simulation View",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the Layer view.",
|
||||
"description": "Provides the Simulation view.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
BIN
plugins/SimulationView/resources/nozzle.stl
Normal file
BIN
plugins/SimulationView/resources/nozzle.stl
Normal file
Binary file not shown.
79
plugins/SimulationView/resources/simulation_pause.svg
Normal file
79
plugins/SimulationView/resources/simulation_pause.svg
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="6mm"
|
||||
height="6mm"
|
||||
viewBox="0 0 5.9999999 6"
|
||||
version="1.1"
|
||||
id="svg877"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="simulation_pause2.svg">
|
||||
<defs
|
||||
id="defs871" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="15.839192"
|
||||
inkscape:cx="-5.3551409"
|
||||
inkscape:cy="17.386031"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2880"
|
||||
inkscape:window-height="1675"
|
||||
inkscape:window-x="-13"
|
||||
inkscape:window-y="-13"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata874">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-11.163774,-122.8006)">
|
||||
<g
|
||||
id="g825"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,10.185689,121.85192)">
|
||||
<rect
|
||||
y="5"
|
||||
x="19"
|
||||
height="20"
|
||||
width="2.7552757"
|
||||
id="rect5192"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.34745646;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<rect
|
||||
y="5"
|
||||
x="9"
|
||||
height="20"
|
||||
width="2.7552757"
|
||||
id="rect5192-5"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.34745646;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
78
plugins/SimulationView/resources/simulation_resume.svg
Normal file
78
plugins/SimulationView/resources/simulation_resume.svg
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="6mm"
|
||||
height="6mm"
|
||||
viewBox="0 0 6 6"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="simulation_resume2.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="15.839192"
|
||||
inkscape:cx="-32.404712"
|
||||
inkscape:cy="14.267522"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2880"
|
||||
inkscape:window-height="1675"
|
||||
inkscape:window-x="-13"
|
||||
inkscape:window-y="-13"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(81.024887,-389.647)">
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.50520164;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path847"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="-78.732257"
|
||||
sodipodi:cy="392.65222"
|
||||
sodipodi:r1="3.0592039"
|
||||
sodipodi:r2="1.5296021"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m -75.67305,392.65222 -4.588806,2.64935 v -5.2987 z"
|
||||
inkscape:transform-center-x="0.75529536"
|
||||
inkscape:transform-center-y="0.40090429" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -87,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()
|
||||
|
|
|
|||
|
|
@ -46,21 +46,12 @@ class SolidView(View):
|
|||
self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*theme.getColor("model_unslicable_alt").getRgb()))
|
||||
self._disabled_shader.setUniformValue("u_width", 50.0)
|
||||
|
||||
multi_extrusion = False
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
|
||||
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr)
|
||||
|
||||
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
|
||||
|
||||
if Preferences.getInstance().getValue("view/show_overhang"):
|
||||
if support_angle_stack is not None and Preferences.getInstance().getValue("view/show_overhang"):
|
||||
angle = support_angle_stack.getProperty("support_angle", "value")
|
||||
# Make sure the overhang angle is valid before passing it to the shader
|
||||
# Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)
|
||||
|
|
@ -71,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])
|
||||
|
|
|
|||
|
|
@ -83,8 +83,7 @@ Component
|
|||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
//TODO; It's probably nicer to do this with a dynamic data model instead of hardcoding this.
|
||||
//But you know the drill; time constraints don't result in elegant code.
|
||||
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ Cura.MachineAction
|
|||
|
||||
Column
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
ScrollView
|
||||
|
|
@ -191,8 +191,6 @@ Cura.MachineAction
|
|||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
wrapMode: Text.WordWrap
|
||||
//: Tips label
|
||||
//TODO: get actual link from webteam
|
||||
text: catalog.i18nc("@label", "If your printer is not listed, read the <a href='%1'>network printing troubleshooting guide</a>").arg("https://ultimaker.com/en/troubleshooting");
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
|
@ -200,7 +198,7 @@ Cura.MachineAction
|
|||
}
|
||||
Column
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
visible: base.selectedPrinter ? true : false
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
Label
|
||||
|
|
@ -218,13 +216,13 @@ Cura.MachineAction
|
|||
columns: 2
|
||||
Label
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Type")
|
||||
}
|
||||
Label
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
wrapMode: Text.WordWrap
|
||||
text:
|
||||
{
|
||||
|
|
@ -232,13 +230,13 @@ Cura.MachineAction
|
|||
{
|
||||
if(base.selectedPrinter.printerType == "ultimaker3")
|
||||
{
|
||||
return catalog.i18nc("@label", "Ultimaker 3")
|
||||
return catalog.i18nc("@label Printer name", "Ultimaker 3")
|
||||
} else if(base.selectedPrinter.printerType == "ultimaker3_extended")
|
||||
{
|
||||
return catalog.i18nc("@label", "Ultimaker 3 Extended")
|
||||
return catalog.i18nc("@label Printer name", "Ultimaker 3 Extended")
|
||||
} else
|
||||
{
|
||||
return catalog.i18nc("@label", "Unknown") // We have no idea what type it is. Should not happen 'in the field'
|
||||
return catalog.i18nc("@label Printer name", "Unknown") // We have no idea what type it is. Should not happen 'in the field'
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -249,25 +247,25 @@ Cura.MachineAction
|
|||
}
|
||||
Label
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Firmware version")
|
||||
}
|
||||
Label
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
wrapMode: Text.WordWrap
|
||||
text: base.selectedPrinter ? base.selectedPrinter.firmwareVersion : ""
|
||||
}
|
||||
Label
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Address")
|
||||
}
|
||||
Label
|
||||
{
|
||||
width: (parent.width * 0.5) | 0
|
||||
width: Math.floor(parent.width * 0.5)
|
||||
wrapMode: Text.WordWrap
|
||||
text: base.selectedPrinter ? base.selectedPrinter.ipAddress : ""
|
||||
}
|
||||
|
|
@ -278,12 +276,8 @@ Cura.MachineAction
|
|||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text:{
|
||||
if (base.selectedPrinter == undefined)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
// The property cluster size does not exist for older UM3 devices.
|
||||
if(base.selectedPrinter != undefined && base.selectedPrinter.clusterSize == null || base.selectedPrinter.clusterSize == 1)
|
||||
if(!base.selectedPrinter || base.selectedPrinter.clusterSize == null || base.selectedPrinter.clusterSize == 1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ Component
|
|||
}
|
||||
return (sourceSize.width / sourceSize.height) > (maximumWidth / maximumHeight);
|
||||
}
|
||||
property real _width: Math.min(maximumWidth, sourceSize.width)
|
||||
property real _height: Math.min(maximumHeight, sourceSize.height)
|
||||
width: proportionalHeight ? _width : sourceSize.width * _height / sourceSize.height
|
||||
height: !proportionalHeight ? _height : sourceSize.height * _width / sourceSize.width
|
||||
property real _width: Math.floor(Math.min(maximumWidth, sourceSize.width))
|
||||
property real _height: Math.floor(Math.min(maximumHeight, sourceSize.height))
|
||||
width: proportionalHeight ? _width : Math.floor(sourceSize.width * _height / sourceSize.height)
|
||||
height: !proportionalHeight ? _height : Math.floor(sourceSize.height * _width / sourceSize.width)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
onVisibleChanged:
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
|||
self._can_pause = True
|
||||
self._can_abort = True
|
||||
self._can_pre_heat_bed = False
|
||||
self._can_control_manually = False
|
||||
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
||||
|
||||
self._cleanupRequest()
|
||||
|
|
@ -220,7 +221,9 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
|||
self.setPrinters(json_data)
|
||||
|
||||
def materialHotendChangedMessage(self, callback):
|
||||
pass # Do nothing.
|
||||
# When there is just one printer, the activate configuration option is enabled
|
||||
if (self._cluster_size == 1):
|
||||
super().materialHotendChangedMessage(callback = callback)
|
||||
|
||||
def _startCameraStream(self):
|
||||
## Request new image
|
||||
|
|
@ -483,7 +486,7 @@ class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinte
|
|||
|
||||
printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"])
|
||||
if printer_name is None:
|
||||
printer_name = i18n_catalog.i18nc("@label", "Unknown")
|
||||
printer_name = i18n_catalog.i18nc("@label Printer name", "Unknown")
|
||||
|
||||
message_text = (i18n_catalog.i18nc("@info:status",
|
||||
"Printer '{printer_name}' has finished printing '{job_name}'.")
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._target_bed_temperature = 0
|
||||
self._processing_preheat_requests = True
|
||||
|
||||
self._can_control_manually = False
|
||||
|
||||
self.setPriority(3) # Make sure the output device gets selected above local file output
|
||||
self.setName(key)
|
||||
self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network"))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Item
|
|||
id: extruderInfo
|
||||
property var printCoreConfiguration
|
||||
|
||||
width: parent.width / 2
|
||||
width: Math.floor(parent.width / 2)
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Rectangle
|
|||
case "maintenance": // TODO: new string
|
||||
case "unknown":
|
||||
default:
|
||||
return catalog.i18nc("@label", "Unknown");
|
||||
return catalog.i18nc("@label Printer status", "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ Rectangle
|
|||
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width / 3
|
||||
width: Math.floor(parent.width / 3)
|
||||
height: parent.height
|
||||
|
||||
Label // Print job name
|
||||
|
|
@ -131,7 +131,7 @@ Rectangle
|
|||
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width / 3 * 2
|
||||
width: Math.floor(parent.width / 3 * 2)
|
||||
height: parent.height
|
||||
|
||||
Label // Friendly machine name
|
||||
|
|
@ -139,7 +139,7 @@ Rectangle
|
|||
id: printerNameLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width
|
||||
width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width)
|
||||
text: printer.friendly_name
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
elide: Text.ElideRight
|
||||
|
|
@ -149,7 +149,7 @@ Rectangle
|
|||
{
|
||||
id: printerTypeLabel
|
||||
anchors.top: printerNameLabel.bottom
|
||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
||||
width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
text: printer.machine_variant
|
||||
anchors.left: parent.left
|
||||
elide: Text.ElideRight
|
||||
|
|
@ -183,7 +183,7 @@ Rectangle
|
|||
id: extruderInfo
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
||||
width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
height: childrenRect.height
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
|
@ -217,7 +217,7 @@ Rectangle
|
|||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: showExtended ? parent.height: printProgressTitleBar.height
|
||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
||||
width: Math.floor(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: lineColor
|
||||
radius: cornerRadius
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ Item
|
|||
{
|
||||
id: cameraImage
|
||||
width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
|
||||
height: (sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width
|
||||
height: Math.floor((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
z: 1
|
||||
|
|
|
|||
|
|
@ -115,8 +115,22 @@ Item
|
|||
{
|
||||
tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura")
|
||||
text: catalog.i18nc("@action:button", "Activate Configuration")
|
||||
visible: printerConnected
|
||||
visible: printerConnected && !isClusterPrinter()
|
||||
onClicked: manager.loadConfigurationFromPrinter()
|
||||
|
||||
function isClusterPrinter() {
|
||||
if(Cura.MachineManager.printerOutputDevices.length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize;
|
||||
// This is not a cluster printer or the cluster it is just one printer
|
||||
if(clusterSize == undefined || clusterSize == 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,12 +91,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
|||
# This will create the view if its not already created.
|
||||
def spawnFirmwareInterface(self, serial_port):
|
||||
if self._firmware_view is None:
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml"))
|
||||
component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
self._firmware_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._firmware_context.setContextProperty("manager", self)
|
||||
self._firmware_view = component.create(self._firmware_context)
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
|
||||
self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
self._firmware_view.show()
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ class UM2UpgradeSelection(MachineAction):
|
|||
def setHasVariants(self, has_variants = True):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
variant_container = global_container_stack.variant
|
||||
variant_index = global_container_stack.getContainerIndex(variant_container)
|
||||
variant_container = global_container_stack.extruders["0"].variant
|
||||
|
||||
if has_variants:
|
||||
if "has_variants" in global_container_stack.getMetaData():
|
||||
|
|
@ -52,7 +51,7 @@ class UM2UpgradeSelection(MachineAction):
|
|||
search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" }
|
||||
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
global_container_stack.variant = containers[0]
|
||||
global_container_stack.extruders["0"].variant = containers[0]
|
||||
else:
|
||||
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
||||
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
||||
|
|
@ -60,6 +59,6 @@ class UM2UpgradeSelection(MachineAction):
|
|||
global_container_stack.removeMetaDataEntry("has_variants")
|
||||
|
||||
# Set the variant container to an empty variant
|
||||
global_container_stack.variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||
global_container_stack.extruders["0"].variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.emit()
|
||||
|
|
|
|||
49
plugins/UserAgreementPlugin/UserAgreement.py
Normal file
49
plugins/UserAgreementPlugin/UserAgreement.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Logger import Logger
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtSlot
|
||||
|
||||
import os.path
|
||||
|
||||
class UserAgreement(QObject, Extension):
|
||||
def __init__(self, parent = None):
|
||||
super(UserAgreement, self).__init__()
|
||||
self._user_agreement_window = None
|
||||
self._user_agreement_context = None
|
||||
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
||||
Preferences.getInstance().addPreference("general/accepted_user_agreement", False)
|
||||
|
||||
def _onEngineCreated(self):
|
||||
if not Preferences.getInstance().getValue("general/accepted_user_agreement"):
|
||||
self.showUserAgreement()
|
||||
|
||||
def showUserAgreement(self):
|
||||
if not self._user_agreement_window:
|
||||
self.createUserAgreementWindow()
|
||||
|
||||
self._user_agreement_window.show()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def didAgree(self, userChoice):
|
||||
if userChoice:
|
||||
Logger.log("i", "User agreed to the user agreement")
|
||||
Preferences.getInstance().setValue("general/accepted_user_agreement", True)
|
||||
self._user_agreement_window.hide()
|
||||
else:
|
||||
Logger.log("i", "User did NOT agree to the user agreement")
|
||||
Preferences.getInstance().setValue("general/accepted_user_agreement", False)
|
||||
CuraApplication.getInstance().quit()
|
||||
CuraApplication.getInstance().setNeedToShowUserAgreement(False)
|
||||
|
||||
def createUserAgreementWindow(self):
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "UserAgreement.qml")
|
||||
self._user_agreement_window = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
64
plugins/UserAgreementPlugin/UserAgreement.qml
Normal file
64
plugins/UserAgreementPlugin/UserAgreement.qml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: baseDialog
|
||||
minimumWidth: Math.floor(UM.Theme.getSize("modal_window_minimum").width * 0.75)
|
||||
minimumHeight: Math.floor(UM.Theme.getSize("modal_window_minimum").height * 0.5)
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
title: catalog.i18nc("@title:window", "User Agreement")
|
||||
|
||||
TextArea
|
||||
{
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
anchors.bottom: buttonRow.top
|
||||
text: ' <center><h3>DISCLAIMER BY ULTIMAKER</h3></center>
|
||||
<p>PLEASE READ THIS DISCLAIMER CAREFULLY.</p>
|
||||
<p>EXCEPT WHEN OTHERWISE STATED IN WRITING, ULTIMAKER PROVIDES ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE “AS IS” WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF ULTIMAKER SOFTWARE IS WITH YOU.</p>
|
||||
<p>UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, IN NO EVENT WILL ULTIMAKER BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE ANY ULTIMAKER SOFTWARE OR THIRD PARTY SOFTWARE.</p>
|
||||
'
|
||||
readOnly: true;
|
||||
textFormat: TextEdit.RichText
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: buttonRow
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
UM.I18nCatalog { id: catalog; name:"cura" }
|
||||
|
||||
Button
|
||||
{
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@action:button", "I understand and agree")
|
||||
onClicked: {
|
||||
manager.didAgree(true)
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
anchors.left: parent.left
|
||||
text: catalog.i18nc("@action:button", "I don't agree")
|
||||
onClicked: {
|
||||
manager.didAgree(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
manager.didAgree(false)
|
||||
}
|
||||
}
|
||||
10
plugins/UserAgreementPlugin/__init__.py
Normal file
10
plugins/UserAgreementPlugin/__init__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import UserAgreement
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return {"extension": UserAgreement.UserAgreement()}
|
||||
8
plugins/UserAgreementPlugin/plugin.json
Normal file
8
plugins/UserAgreementPlugin/plugin.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "UserAgreement",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Ask the user once if he/she agrees with our license",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import configparser #To parse preference files.
|
||||
import io #To serialise the preference files afterwards.
|
||||
import os
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from UM.Resources import Resources
|
||||
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
# a list of all legacy "Not Supported" quality profiles
|
||||
_OLD_NOT_SUPPORTED_PROFILES = [
|
||||
"um2p_pp_0.25_normal",
|
||||
"um2p_tpu_0.8_normal",
|
||||
"um3_bb0.4_ABS_Fast_Print",
|
||||
"um3_bb0.4_ABS_Superdraft_Print",
|
||||
"um3_bb0.4_CPEP_Fast_Print",
|
||||
"um3_bb0.4_CPEP_Superdraft_Print",
|
||||
"um3_bb0.4_CPE_Fast_Print",
|
||||
"um3_bb0.4_CPE_Superdraft_Print",
|
||||
"um3_bb0.4_Nylon_Fast_Print",
|
||||
"um3_bb0.4_Nylon_Superdraft_Print",
|
||||
"um3_bb0.4_PC_Fast_Print",
|
||||
"um3_bb0.4_PLA_Fast_Print",
|
||||
"um3_bb0.4_PLA_Superdraft_Print",
|
||||
"um3_bb0.4_PP_Fast_Print",
|
||||
"um3_bb0.4_PP_Superdraft_Print",
|
||||
"um3_bb0.4_TPU_Fast_Print",
|
||||
"um3_bb0.4_TPU_Superdraft_Print",
|
||||
"um3_bb0.8_ABS_Fast_Print",
|
||||
"um3_bb0.8_ABS_Superdraft_Print",
|
||||
"um3_bb0.8_CPEP_Fast_Print",
|
||||
"um3_bb0.8_CPEP_Superdraft_Print",
|
||||
"um3_bb0.8_CPE_Fast_Print",
|
||||
"um3_bb0.8_CPE_Superdraft_Print",
|
||||
"um3_bb0.8_Nylon_Fast_Print",
|
||||
"um3_bb0.8_Nylon_Superdraft_Print",
|
||||
"um3_bb0.8_PC_Fast_Print",
|
||||
"um3_bb0.8_PC_Superdraft_Print",
|
||||
"um3_bb0.8_PLA_Fast_Print",
|
||||
"um3_bb0.8_PLA_Superdraft_Print",
|
||||
"um3_bb0.8_PP_Fast_Print",
|
||||
"um3_bb0.8_PP_Superdraft_Print",
|
||||
"um3_bb0.8_TPU_Fast_print",
|
||||
"um3_bb0.8_TPU_Superdraft_Print",
|
||||
]
|
||||
|
||||
|
||||
# Some containers have their specific empty containers, those need to be set correctly.
|
||||
_EMPTY_CONTAINER_DICT = {
|
||||
"1": "empty_quality_changes",
|
||||
"2": "empty_quality",
|
||||
"3": "empty_material",
|
||||
"4": "empty_variant",
|
||||
}
|
||||
|
||||
|
||||
class VersionUpgrade30to31(VersionUpgrade):
|
||||
## Gets the version number from a CFG file in Uranium's 3.0 format.
|
||||
#
|
||||
# Since the format may change, this is implemented for the 3.0 format only
|
||||
# and needs to be included in the version upgrade system rather than
|
||||
# globally in Uranium.
|
||||
#
|
||||
# \param serialised The serialised form of a CFG file.
|
||||
# \return The version number stored in the CFG file.
|
||||
# \raises ValueError The format of the version number in the file is
|
||||
# incorrect.
|
||||
# \raises KeyError The format of the file is incorrect.
|
||||
def getCfgVersion(self, serialised):
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
||||
## Upgrades a preferences file from version 3.0 to 3.1.
|
||||
#
|
||||
# \param serialised The serialised form of a preferences file.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradePreferences(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
# Update version numbers
|
||||
if "general" not in parser:
|
||||
parser["general"] = {}
|
||||
parser["general"]["version"] = "5"
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["metadata"]["setting_version"] = "4"
|
||||
|
||||
# Re-serialise the file.
|
||||
output = io.StringIO()
|
||||
parser.write(output)
|
||||
return [filename], [output.getvalue()]
|
||||
|
||||
## Upgrades the given instance container file from version 3.0 to 3.1.
|
||||
#
|
||||
# \param serialised The serialised form of the container file.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradeInstanceContainer(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
for each_section in ("general", "metadata"):
|
||||
if not parser.has_section(each_section):
|
||||
parser.add_section(each_section)
|
||||
|
||||
# Copy global quality changes to extruder quality changes for single extrusion machines
|
||||
if parser["metadata"]["type"] == "quality_changes":
|
||||
all_quality_changes = self._getSingleExtrusionMachineQualityChanges(parser)
|
||||
# Note that DO NOT!!! use the quality_changes returned from _getSingleExtrusionMachineQualityChanges().
|
||||
# Those are loaded from the hard drive which are original files that haven't been upgraded yet.
|
||||
# NOTE 2: The number can be 0 or 1 depends on whether you are loading it from the qualities folder or
|
||||
# from a project file. When you load from a project file, the custom profile may not be in cura
|
||||
# yet, so you will get 0.
|
||||
if len(all_quality_changes) <= 1 and not parser.has_option("metadata", "extruder"):
|
||||
self._createExtruderQualityChangesForSingleExtrusionMachine(filename, parser)
|
||||
|
||||
# Update version numbers
|
||||
parser["general"]["version"] = "2"
|
||||
parser["metadata"]["setting_version"] = "4"
|
||||
|
||||
# Re-serialise the file.
|
||||
output = io.StringIO()
|
||||
parser.write(output)
|
||||
return [filename], [output.getvalue()]
|
||||
|
||||
|
||||
## Upgrades a container stack from version 3.0 to 3.1.
|
||||
#
|
||||
# \param serialised The serialised form of a container stack.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradeStack(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
for each_section in ("general", "metadata"):
|
||||
if not parser.has_section(each_section):
|
||||
parser.add_section(each_section)
|
||||
|
||||
# change "not supported" quality profiles to empty because they no longer exist
|
||||
if parser.has_section("containers"):
|
||||
if parser.has_option("containers", "2"):
|
||||
quality_profile_id = parser["containers"]["2"]
|
||||
if quality_profile_id in _OLD_NOT_SUPPORTED_PROFILES:
|
||||
parser["containers"]["2"] = "empty_quality"
|
||||
|
||||
# fix empty containers
|
||||
for key, specific_empty_container in _EMPTY_CONTAINER_DICT.items():
|
||||
if parser.has_option("containers", key) and parser["containers"][key] == "empty":
|
||||
parser["containers"][key] = specific_empty_container
|
||||
|
||||
# Update version numbers
|
||||
if "general" not in parser:
|
||||
parser["general"] = {}
|
||||
parser["general"]["version"] = "3"
|
||||
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["metadata"]["setting_version"] = "4"
|
||||
|
||||
# Re-serialise the file.
|
||||
output = io.StringIO()
|
||||
parser.write(output)
|
||||
return [filename], [output.getvalue()]
|
||||
|
||||
def _getSingleExtrusionMachineQualityChanges(self, quality_changes_container):
|
||||
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||
quality_changes_containers = []
|
||||
|
||||
for item in os.listdir(quality_changes_dir):
|
||||
file_path = os.path.join(quality_changes_dir, item)
|
||||
if not os.path.isfile(file_path):
|
||||
continue
|
||||
|
||||
parser = configparser.ConfigParser()
|
||||
try:
|
||||
parser.read([file_path])
|
||||
except:
|
||||
# skip, it is not a valid stack file
|
||||
continue
|
||||
|
||||
if not parser.has_option("metadata", "type"):
|
||||
continue
|
||||
if "quality_changes" != parser["metadata"]["type"]:
|
||||
continue
|
||||
|
||||
if not parser.has_option("general", "name"):
|
||||
continue
|
||||
if quality_changes_container["general"]["name"] != parser["general"]["name"]:
|
||||
continue
|
||||
|
||||
quality_changes_containers.append(parser)
|
||||
|
||||
return quality_changes_containers
|
||||
|
||||
def _createExtruderQualityChangesForSingleExtrusionMachine(self, filename, global_quality_changes):
|
||||
suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower())
|
||||
machine_name = os.path.os.path.basename(filename).replace(".inst.cfg", "").replace(suffix, "")
|
||||
new_filename = machine_name + "_" + "fdmextruder" + suffix
|
||||
|
||||
extruder_quality_changes_parser = configparser.ConfigParser()
|
||||
extruder_quality_changes_parser.add_section("general")
|
||||
extruder_quality_changes_parser["general"]["version"] = str(2)
|
||||
extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"]
|
||||
extruder_quality_changes_parser["general"]["definition"] = global_quality_changes["general"]["definition"]
|
||||
|
||||
extruder_quality_changes_parser.add_section("metadata")
|
||||
extruder_quality_changes_parser["metadata"]["quality_type"] = global_quality_changes["metadata"]["quality_type"]
|
||||
extruder_quality_changes_parser["metadata"]["type"] = global_quality_changes["metadata"]["type"]
|
||||
extruder_quality_changes_parser["metadata"]["setting_version"] = str(4)
|
||||
extruder_quality_changes_parser["metadata"]["extruder"] = "fdmextruder"
|
||||
|
||||
extruder_quality_changes_output = io.StringIO()
|
||||
extruder_quality_changes_parser.write(extruder_quality_changes_output)
|
||||
extruder_quality_changes_filename = quote_plus(new_filename) + ".inst.cfg"
|
||||
|
||||
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||
|
||||
with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w") as f:
|
||||
f.write(extruder_quality_changes_output.getvalue())
|
||||
56
plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
Normal file
56
plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import VersionUpgrade30to31
|
||||
|
||||
upgrade = VersionUpgrade30to31.VersionUpgrade30to31()
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("preferences", 5000003): ("preferences", 5000004, upgrade.upgradePreferences),
|
||||
|
||||
("machine_stack", 3000003): ("machine_stack", 3000004, upgrade.upgradeStack),
|
||||
("extruder_train", 3000003): ("extruder_train", 3000004, upgrade.upgradeStack),
|
||||
|
||||
("quality_changes", 2000003): ("quality_changes", 2000004, upgrade.upgradeInstanceContainer),
|
||||
("user", 2000003): ("user", 2000004, upgrade.upgradeInstanceContainer),
|
||||
("quality", 2000003): ("quality", 2000004, upgrade.upgradeInstanceContainer),
|
||||
("definition_changes", 2000003): ("definition_changes", 2000004, upgrade.upgradeInstanceContainer),
|
||||
("variant", 2000003): ("variant", 2000004, upgrade.upgradeInstanceContainer)
|
||||
},
|
||||
"sources": {
|
||||
"preferences": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"."}
|
||||
},
|
||||
"machine_stack": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./machine_instances"}
|
||||
},
|
||||
"extruder_train": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./extruders"}
|
||||
},
|
||||
"quality_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./quality"}
|
||||
},
|
||||
"user": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./user"}
|
||||
},
|
||||
"definition_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./definition_changes"}
|
||||
},
|
||||
"variant": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./variants"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return { "version_upgrade": upgrade }
|
||||
8
plugins/VersionUpgrade/VersionUpgrade30to31/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade30to31/plugin.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Version Upgrade 3.0 to 3.1",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -56,7 +56,8 @@ class XRayView(View):
|
|||
# Currently the RenderPass constructor requires a size > 0
|
||||
# This should be fixed in RenderPass's constructor.
|
||||
self._xray_pass = XRayPass.XRayPass(1, 1)
|
||||
self.getRenderer().addRenderPass(self._xray_pass)
|
||||
|
||||
self.getRenderer().addRenderPass(self._xray_pass)
|
||||
|
||||
if not self._xray_composite_shader:
|
||||
self._xray_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("XRayView"), "xray_composite.shader"))
|
||||
|
|
@ -74,5 +75,6 @@ class XRayView(View):
|
|||
self._composite_pass.setCompositeShader(self._xray_composite_shader)
|
||||
|
||||
if event.type == Event.ViewDeactivateEvent:
|
||||
self.getRenderer().removeRenderPass(self._xray_pass)
|
||||
self._composite_pass.setLayerBindings(self._old_layer_bindings)
|
||||
self._composite_pass.setCompositeShader(self._old_composite_shader)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
@classmethod
|
||||
def xmlVersionToSettingVersion(cls, xml_version: str) -> int:
|
||||
if xml_version == "1.3":
|
||||
return 3
|
||||
return CuraApplication.SettingVersion
|
||||
return 0 #Older than 1.3.
|
||||
|
||||
def getInheritedFiles(self):
|
||||
|
|
@ -399,7 +399,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
def getVersionFromSerialized(cls, serialized: str) -> Optional[int]:
|
||||
data = ET.fromstring(serialized)
|
||||
|
||||
version = 1
|
||||
version = XmlMaterialProfile.Version
|
||||
# get setting version
|
||||
if "version" in data.attrib:
|
||||
setting_version = cls.xmlVersionToSettingVersion(data.attrib["version"])
|
||||
|
|
@ -409,11 +409,11 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
return version * 1000000 + setting_version
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
def deserialize(self, serialized):
|
||||
def deserialize(self, serialized, file_name = None):
|
||||
containers_to_add = []
|
||||
# update the serialized data first
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
serialized = ContainerInterface.deserialize(self, serialized)
|
||||
serialized = ContainerInterface.deserialize(self, serialized, file_name)
|
||||
|
||||
try:
|
||||
data = ET.fromstring(serialized)
|
||||
|
|
@ -502,8 +502,6 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
elif key in self.__unmapped_settings:
|
||||
if key == "hardware compatible":
|
||||
common_compatibility = self._parseCompatibleValue(entry.text)
|
||||
else:
|
||||
Logger.log("d", "Unsupported material setting %s", key)
|
||||
self._cached_values = common_setting_values # from InstanceContainer ancestor
|
||||
|
||||
meta_data["compatible"] = common_compatibility
|
||||
|
|
|
|||
|
|
@ -5,24 +5,16 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from .XmlMaterialProfile import XmlMaterialProfile
|
||||
|
||||
|
||||
class XmlMaterialUpgrader(VersionUpgrade):
|
||||
def getXmlVersion(self, serialized):
|
||||
data = ET.fromstring(serialized)
|
||||
|
||||
version = 1
|
||||
# get setting version
|
||||
if "version" in data.attrib:
|
||||
setting_version = self._xmlVersionToSettingVersion(data.attrib["version"])
|
||||
else:
|
||||
setting_version = self._xmlVersionToSettingVersion("1.2")
|
||||
|
||||
return version * 1000000 + setting_version
|
||||
return XmlMaterialProfile.getVersionFromSerialized(serialized)
|
||||
|
||||
def _xmlVersionToSettingVersion(self, xml_version: str) -> int:
|
||||
if xml_version == "1.3":
|
||||
return 2
|
||||
return 0 #Older than 1.3.
|
||||
return XmlMaterialProfile.xmlVersionToSettingVersion(xml_version)
|
||||
|
||||
def upgradeMaterial(self, serialised, filename):
|
||||
data = ET.fromstring(serialised)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ def getMetaData():
|
|||
"mimetype": "application/x-ultimaker-material-profile"
|
||||
},
|
||||
"version_upgrade": {
|
||||
("materials", 1000000): ("materials", 1000003, upgrader.upgradeMaterial),
|
||||
("materials", 1000000): ("materials", 1000004, upgrader.upgradeMaterial),
|
||||
},
|
||||
"sources": {
|
||||
"materials": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue