Merge branch 'master' into master-1

This commit is contained in:
Lipu Fei 2019-12-11 10:57:50 +01:00 committed by GitHub
commit 849ac3551d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2286 changed files with 195832 additions and 76181 deletions

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from typing import List, Optional, Union, TYPE_CHECKING
import os.path
import zipfile
@ -9,27 +9,26 @@ import numpy
import Savitar
from UM.Application import Application
from UM.Logger import Logger
from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator
from UM.Scene.SceneNode import SceneNode #For typing.
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.CuraApplication import CuraApplication
from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False
try:
if not MYPY:
if not TYPE_CHECKING:
import xml.etree.cElementTree as ET
except ImportError:
Logger.log("w", "Unable to load cElementTree, switching to slower version")
@ -55,7 +54,7 @@ class ThreeMFReader(MeshReader):
self._unit = None
self._object_count = 0 # Used to name objects as there is no node name yet.
def _createMatrixFromTransformationString(self, transformation):
def _createMatrixFromTransformationString(self, transformation: str) -> Matrix:
if transformation == "":
return Matrix()
@ -85,13 +84,13 @@ class ThreeMFReader(MeshReader):
return temp_mat
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scene node.
# \returns Uranium scene node.
def _convertSavitarNodeToUMNode(self, savitar_node):
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
# \returns Scene node.
def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode) -> Optional[SceneNode]:
self._object_count += 1
node_name = "Object %s" % self._object_count
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
um_node.addDecorator(BuildPlateDecorator(active_build_plate))
@ -122,7 +121,7 @@ class ThreeMFReader(MeshReader):
# Add the setting override decorator, so we can add settings to this node.
if settings:
global_container_stack = Application.getInstance().getGlobalContainerStack()
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
# Ensure the correct next container for the SettingOverride decorator is set.
if global_container_stack:
@ -132,7 +131,7 @@ class ThreeMFReader(MeshReader):
um_node.callDecoration("setActiveExtruder", default_stack.getId())
# Get the definition & set it
definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
definition_id = ContainerTree.getInstance().machines[global_container_stack.definition.getId()].quality_definition
um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
setting_container = um_node.callDecoration("getStack").getTop()
@ -161,7 +160,7 @@ class ThreeMFReader(MeshReader):
um_node.addDecorator(sliceable_decorator)
return um_node
def _read(self, file_name):
def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
result = []
self._object_count = 0 # Used to name objects as there is no node name yet.
# The base object of 3mf is a zipped archive.
@ -181,12 +180,13 @@ class ThreeMFReader(MeshReader):
mesh_data = um_node.getMeshData()
if mesh_data is not None:
extents = mesh_data.getExtents()
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
transform_matrix.setByTranslation(center_vector)
if extents is not None:
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
transform_matrix.setByTranslation(center_vector)
transform_matrix.multiply(um_node.getLocalTransformation())
um_node.setTransformation(transform_matrix)
global_container_stack = Application.getInstance().getGlobalContainerStack()
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
# Create a transformation Matrix to convert from 3mf worldspace into ours.
# First step: flip the y and z axis.
@ -215,17 +215,20 @@ class ThreeMFReader(MeshReader):
um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix))
# Check if the model is positioned below the build plate and honor that when loading project files.
if um_node.getMeshData() is not None:
minimum_z_value = um_node.getMeshData().getExtents(um_node.getWorldTransformation()).minimum.y # y is z in transformation coordinates
if minimum_z_value < 0:
um_node.addDecorator(ZOffsetDecorator())
um_node.callDecoration("setZOffset", minimum_z_value)
node_meshdata = um_node.getMeshData()
if node_meshdata is not None:
aabb = node_meshdata.getExtents(um_node.getWorldTransformation())
if aabb is not None:
minimum_z_value = aabb.minimum.y # y is z in transformation coordinates
if minimum_z_value < 0:
um_node.addDecorator(ZOffsetDecorator())
um_node.callDecoration("setZOffset", minimum_z_value)
result.append(um_node)
except Exception:
Logger.logException("e", "An exception occurred in 3mf reader.")
return None
return []
return result

View file

@ -1,10 +1,10 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from configparser import ConfigParser
import zipfile
import os
from typing import Dict, List, Tuple, cast
from typing import cast, Dict, List, Optional, Tuple
import xml.etree.ElementTree as ET
@ -14,7 +14,6 @@ from UM.Application import Application
from UM.Logger import Logger
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Signal import postponeSignals, CompressTechnique
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
@ -24,21 +23,25 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Job import Job
from UM.Preferences import Preferences
from cura.Machines.VariantType import VariantType
from cura.Machines.ContainerTree import ContainerTree
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.IntentManager import IntentManager
from cura.Settings.CuraContainerStack import _ContainerIndexes
from cura.CuraApplication import CuraApplication
from cura.Utils.Threading import call_on_qt_thread
from PyQt5.QtCore import QCoreApplication
from .WorkspaceDialog import WorkspaceDialog
i18n_catalog = i18nCatalog("cura")
class ContainerInfo:
def __init__(self, file_name: str, serialized: str, parser: ConfigParser) -> None:
def __init__(self, file_name: Optional[str], serialized: Optional[str], parser: Optional[ConfigParser]) -> None:
self.file_name = file_name
self.serialized = serialized
self.parser = parser
@ -58,7 +61,11 @@ class MachineInfo:
self.container_id = None
self.name = None
self.definition_id = None
self.metadata_dict = {} # type: Dict[str, str]
self.quality_type = None
self.intent_category = None
self.custom_quality_name = None
self.quality_changes_info = None
self.variant_info = None
@ -78,6 +85,7 @@ class ExtruderInfo:
self.definition_changes_info = None
self.user_changes_info = None
self.intent_info = None
## Base implementation for reading 3MF workspace files.
@ -226,6 +234,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
else:
Logger.log("w", "Unknown definition container type %s for %s",
definition_container_type, definition_container_file)
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread()
if machine_definition_container_count != 1:
@ -252,13 +261,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
containers_found_dict["material"] = True
if not self._container_registry.isReadOnly(container_id): # Only non readonly materials can be in conflict
material_conflict = True
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread()
# Check if any quality_changes instance container is in conflict.
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
quality_name = ""
custom_quality_name = ""
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
intent_name = ""
intent_category = ""
num_settings_overridden_by_quality_changes = 0 # How many settings are changed by the quality changes
num_user_settings = 0
quality_changes_conflict = False
@ -296,7 +308,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
custom_quality_name = parser["general"]["name"]
values = parser["values"] if parser.has_section("values") else dict()
num_settings_overriden_by_quality_changes += len(values)
num_settings_overridden_by_quality_changes += len(values)
# Check if quality changes already exists.
quality_changes = self._container_registry.findInstanceContainers(name = custom_quality_name,
type = "quality_changes")
@ -315,13 +327,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
elif container_type == "quality":
if not quality_name:
quality_name = parser["general"]["name"]
elif container_type == "intent":
if not intent_name:
intent_name = parser["general"]["name"]
intent_category = parser["metadata"]["intent_category"]
elif container_type == "user":
num_user_settings += len(parser["values"])
elif container_type in self._ignored_instance_container_types:
# Ignore certain instance container types
Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
continue
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread()
if self._machine_info.quality_changes_info.global_info is None:
@ -340,7 +356,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# To simplify this, only check if the global stack exists or not
global_stack_id = self._stripFileToId(global_stack_file)
serialized = archive.open(global_stack_file).read().decode("utf-8")
serialized = GlobalStack._updateSerialized(serialized, global_stack_file)
machine_name = self._getMachineNameFromSerializedStack(serialized)
self._machine_info.metadata_dict = self._getMetaDataDictFromSerializedStack(serialized)
stacks = self._container_registry.findContainerStacks(name = machine_name, type = "machine")
self._is_same_machine_type = True
existing_global_stack = None
@ -396,7 +415,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
variant_id = parser["containers"][str(_ContainerIndexes.Variant)]
if variant_id not in ("empty", "empty_variant"):
self._machine_info.variant_info = instance_container_info_dict[variant_id]
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread()
# if the global stack is found, we check if there are conflicts in the extruder stacks
@ -418,18 +437,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if parser.has_option("metadata", "enabled"):
extruder_info.enabled = parser["metadata"]["enabled"]
if variant_id not in ("empty", "empty_variant"):
extruder_info.variant_info = instance_container_info_dict[variant_id]
if variant_id in instance_container_info_dict:
extruder_info.variant_info = instance_container_info_dict[variant_id]
if material_id not in ("empty", "empty_material"):
root_material_id = reverse_material_id_dict[material_id]
extruder_info.root_material_id = root_material_id
definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)]
if definition_changes_id not in ("empty", "empty_definition_changes"):
extruder_info.definition_changes_info = instance_container_info_dict[definition_changes_id]
user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)]
if user_changes_id not in ("empty", "empty_user_changes"):
extruder_info.user_changes_info = instance_container_info_dict[user_changes_id]
self._machine_info.extruder_info_dict[position] = extruder_info
intent_id = parser["containers"][str(_ContainerIndexes.Intent)]
if intent_id not in ("empty", "empty_intent"):
extruder_info.intent_info = instance_container_info_dict[intent_id]
if not machine_conflict and containers_found_dict["machine"]:
if position not in global_stack.extruders:
continue
@ -494,6 +521,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._machine_info.definition_id = machine_definition_id
self._machine_info.quality_type = quality_type
self._machine_info.custom_quality_name = quality_name
self._machine_info.intent_category = intent_category
if machine_conflict and not self._is_same_machine_type:
machine_conflict = False
@ -514,7 +542,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._dialog.setNumVisibleSettings(num_visible_settings)
self._dialog.setQualityName(quality_name)
self._dialog.setQualityType(quality_type)
self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
self._dialog.setIntentName(intent_name)
self._dialog.setNumSettingsOverriddenByQualityChanges(num_settings_overridden_by_quality_changes)
self._dialog.setNumUserSettings(num_user_settings)
self._dialog.setActiveMode(active_mode)
self._dialog.setMachineName(machine_name)
@ -557,26 +586,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# \param file_name
@call_on_qt_thread
def read(self, file_name):
container_registry = ContainerRegistry.getInstance()
signals = [container_registry.containerAdded,
container_registry.containerRemoved,
container_registry.containerMetaDataChanged]
#
# We now have different managers updating their lookup tables upon container changes. It is critical to make
# sure that the managers have a complete set of data when they update.
#
# In project loading, lots of the container-related signals are loosely emitted, which can create timing gaps
# for incomplete data update or other kinds of issues to happen.
#
# To avoid this, we postpone all signals so they don't get emitted immediately. But, please also be aware that,
# because of this, do not expect to have the latest data in the lookup tables in project loading.
#
with postponeSignals(*signals, compress = CompressTechnique.NoCompression):
return self._read(file_name)
def _read(self, file_name):
application = CuraApplication.getInstance()
material_manager = application.getMaterialManager()
archive = zipfile.ZipFile(file_name, "r")
@ -647,6 +657,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults.
self._container_registry.addContainer(definition_container)
Job.yieldThread()
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Logger.log("d", "Workspace loading is checking materials...")
# Get all the material files and check if they exist. If not, add them.
@ -673,7 +684,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if self._resolve_strategies["material"] == "override":
# Remove the old materials and then deserialize the one from the project
root_material_id = material_container.getMetaDataEntry("base_file")
material_manager.removeMaterialByRootId(root_material_id)
application.getContainerRegistry().removeContainer(root_material_id)
elif self._resolve_strategies["material"] == "new":
# Note that we *must* deserialize it with a new ID, as multiple containers will be
# auto created & added.
@ -696,6 +707,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
material_container.setDirty(True)
self._container_registry.addContainer(material_container)
Job.yieldThread()
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
# Handle quality changes if any
self._processQualityChanges(global_stack)
@ -726,9 +738,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if self._machine_info.quality_changes_info is None:
return
application = CuraApplication.getInstance()
quality_manager = application.getQualityManager()
# If we have custom profiles, load them
quality_changes_name = self._machine_info.quality_changes_info.name
if self._machine_info.quality_changes_info is not None:
@ -736,12 +745,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._machine_info.quality_changes_info.name)
# Get the correct extruder definition IDs for quality changes
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack.definition)
machine_definition_id_for_quality = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0]
quality_changes_info = self._machine_info.quality_changes_info
quality_changes_quality_type = quality_changes_info.global_info.parser["metadata"]["quality_type"]
quality_changes_intent_category_per_extruder = {position: info.parser["metadata"].get("intent_category", "default") for position, info in quality_changes_info.extruder_info_dict.items()}
quality_changes_name = quality_changes_info.name
create_new = self._resolve_strategies.get("quality_changes") != "override"
@ -752,13 +761,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
quality_changes_name = self._container_registry.uniqueName(quality_changes_name)
for position, container_info in container_info_dict.items():
extruder_stack = None
intent_category = None # type: Optional[str]
if position is not None:
extruder_stack = global_stack.extruders[position]
container = quality_manager._createQualityChanges(quality_changes_quality_type,
quality_changes_name,
global_stack, extruder_stack)
intent_category = quality_changes_intent_category_per_extruder[position]
container = self._createNewQualityChanges(quality_changes_quality_type, intent_category, quality_changes_name, global_stack, extruder_stack)
container_info.container = container
container.setDirty(True)
self._container_registry.addContainer(container)
Logger.log("d", "Created new quality changes container [%s]", container.getId())
@ -781,12 +789,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if not quality_changes_info.extruder_info_dict:
container_info = ContainerInfo(None, None, None)
quality_changes_info.extruder_info_dict["0"] = container_info
# If the global stack we're "targeting" has never been active, but was updated from Cura 3.4,
# it might not have it's extruders set properly.
if not global_stack.extruders:
ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack)
extruder_stack = global_stack.extruders["0"]
intent_category = quality_changes_intent_category_per_extruder["0"]
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
global_stack, extruder_stack)
container = self._createNewQualityChanges(quality_changes_quality_type, intent_category, quality_changes_name, global_stack, extruder_stack)
container_info.container = container
container.setDirty(True)
self._container_registry.addContainer(container)
Logger.log("d", "Created new quality changes container [%s]", container.getId())
@ -812,16 +823,57 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if container_info.container is None:
extruder_stack = global_stack.extruders[position]
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name,
global_stack, extruder_stack)
intent_category = quality_changes_intent_category_per_extruder[position]
container = self._createNewQualityChanges(quality_changes_quality_type, intent_category, quality_changes_name, global_stack, extruder_stack)
container_info.container = container
self._container_registry.addContainer(container)
for key, value in container_info.parser["values"].items():
container_info.container.setProperty(key, "value", value)
self._machine_info.quality_changes_info.name = quality_changes_name
def _clearStack(self, stack):
## Helper class to create a new quality changes profile.
#
# This will then later be filled with the appropriate data.
# \param quality_type The quality type of the new profile.
# \param intent_category The intent category of the new profile.
# \param name The name for the profile. This will later be made unique so
# it doesn't need to be unique yet.
# \param global_stack The global stack showing the configuration that the
# profile should be created for.
# \param extruder_stack The extruder stack showing the configuration that
# the profile should be created for. If this is None, it will be created
# for the global stack.
def _createNewQualityChanges(self, quality_type: str, intent_category: Optional[str], name: str, global_stack: GlobalStack, extruder_stack: Optional[ExtruderStack]) -> InstanceContainer:
container_registry = CuraApplication.getInstance().getContainerRegistry()
base_id = global_stack.definition.getId() if extruder_stack is None else extruder_stack.getId()
new_id = base_id + "_" + name
new_id = new_id.lower().replace(" ", "_")
new_id = container_registry.uniqueName(new_id)
# Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(new_id)
quality_changes.setName(name)
quality_changes.setMetaDataEntry("type", "quality_changes")
quality_changes.setMetaDataEntry("quality_type", quality_type)
if intent_category is not None:
quality_changes.setMetaDataEntry("intent_category", intent_category)
# If we are creating a container for an extruder, ensure we add that to the container.
if extruder_stack is not None:
quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
machine_definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
quality_changes.setDefinition(machine_definition_id)
quality_changes.setMetaDataEntry("setting_version", CuraApplication.getInstance().SettingVersion)
quality_changes.setDirty(True)
return quality_changes
@staticmethod
def _clearStack(stack):
application = CuraApplication.getInstance()
stack.definitionChanges.clear()
@ -880,41 +932,30 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_stack.userChanges.setProperty(key, "value", value)
def _applyVariants(self, global_stack, extruder_stack_dict):
application = CuraApplication.getInstance()
variant_manager = application.getVariantManager()
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
# Take the global variant from the machine info if available.
if self._machine_info.variant_info is not None:
parser = self._machine_info.variant_info.parser
variant_name = parser["general"]["name"]
variant_type = VariantType.BUILD_PLATE
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
if node is not None and node.getContainer() is not None:
global_stack.variant = node.getContainer()
variant_name = self._machine_info.variant_info.parser["general"]["name"]
if variant_name in machine_node.variants:
global_stack.variant = machine_node.variants[variant_name].container
else:
Logger.log("w", "Could not find global variant '{0}'.".format(variant_name))
for position, extruder_stack in extruder_stack_dict.items():
if position not in self._machine_info.extruder_info_dict:
continue
extruder_info = self._machine_info.extruder_info_dict[position]
if extruder_info.variant_info is None:
continue
parser = extruder_info.variant_info.parser
variant_name = parser["general"]["name"]
variant_type = VariantType.NOZZLE
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
if node is not None and node.getContainer() is not None:
extruder_stack.variant = node.getContainer()
# If there is no variant_info, try to use the default variant. Otherwise, any available variant.
node = machine_node.variants.get(machine_node.preferred_variant_name, next(iter(machine_node.variants.values())))
else:
variant_name = extruder_info.variant_info.parser["general"]["name"]
node = ContainerTree.getInstance().machines[global_stack.definition.getId()].variants[variant_name]
extruder_stack.variant = node.container
def _applyMaterials(self, global_stack, extruder_stack_dict):
application = CuraApplication.getInstance()
material_manager = application.getMaterialManager()
# Force update lookup tables first
material_manager.initialize()
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
for position, extruder_stack in extruder_stack_dict.items():
if position not in self._machine_info.extruder_info_dict:
continue
@ -925,18 +966,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
root_material_id = extruder_info.root_material_id
root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
build_plate_id = global_stack.variant.getId()
# get material diameter of this extruder
machine_material_diameter = extruder_stack.getCompatibleMaterialDiameter()
material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
extruder_stack.variant.getName(),
build_plate_id,
machine_material_diameter,
root_material_id)
if material_node is not None and material_node.getContainer() is not None:
extruder_stack.material = material_node.getContainer() # type: InstanceContainer
material_node = machine_node.variants[extruder_stack.variant.getName()].materials[root_material_id]
extruder_stack.material = material_node.container # type: InstanceContainer
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
# Clear all first
@ -952,10 +983,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# prepare the quality to select
self._quality_changes_to_apply = None
self._quality_type_to_apply = None
self._intent_category_to_apply = None
if self._machine_info.quality_changes_info is not None:
self._quality_changes_to_apply = self._machine_info.quality_changes_info.name
else:
self._quality_type_to_apply = self._machine_info.quality_type
self._intent_category_to_apply = self._machine_info.intent_category
# Set enabled/disabled for extruders
for position, extruder_stack in extruder_stack_dict.items():
@ -966,34 +999,38 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_stack.setMetaDataEntry("enabled", "True")
extruder_stack.setMetaDataEntry("enabled", str(extruder_info.enabled))
# Set metadata fields that are missing from the global stack
for key, value in self._machine_info.metadata_dict.items():
if key not in global_stack.getMetaData():
global_stack.setMetaDataEntry(key, value)
def _updateActiveMachine(self, global_stack):
# Actually change the active machine.
machine_manager = Application.getInstance().getMachineManager()
material_manager = Application.getInstance().getMaterialManager()
quality_manager = Application.getInstance().getQualityManager()
# Force update the lookup maps first
material_manager.initialize()
quality_manager.initialize()
container_tree = ContainerTree.getInstance()
machine_manager.setActiveMachine(global_stack.getId())
# Set metadata fields that are missing from the global stack
for key, value in self._machine_info.metadata_dict.items():
if key not in global_stack.getMetaData():
global_stack.setMetaDataEntry(key, value)
if self._quality_changes_to_apply:
quality_changes_group_dict = quality_manager.getQualityChangesGroups(global_stack)
if self._quality_changes_to_apply not in quality_changes_group_dict:
quality_changes_group_list = container_tree.getCurrentQualityChangesGroups()
quality_changes_group = next((qcg for qcg in quality_changes_group_list if qcg.name == self._quality_changes_to_apply), None)
if not quality_changes_group:
Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply)
return
quality_changes_group = quality_changes_group_dict[self._quality_changes_to_apply]
machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True)
else:
self._quality_type_to_apply = self._quality_type_to_apply.lower()
quality_group_dict = quality_manager.getQualityGroups(global_stack)
quality_group_dict = container_tree.getCurrentQualityGroups()
if self._quality_type_to_apply in quality_group_dict:
quality_group = quality_group_dict[self._quality_type_to_apply]
else:
Logger.log("i", "Could not find quality type [%s], switch to default", self._quality_type_to_apply)
preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type")
quality_group_dict = quality_manager.getQualityGroups(global_stack)
quality_group = quality_group_dict.get(preferred_quality_type)
if quality_group is None:
Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type)
@ -1001,10 +1038,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if quality_group is not None:
machine_manager.setQualityGroup(quality_group, no_dialog = True)
# Also apply intent if available
available_intent_category_list = IntentManager.getInstance().currentAvailableIntentCategories()
if self._intent_category_to_apply is not None and self._intent_category_to_apply in available_intent_category_list:
machine_manager.setIntentByCategory(self._intent_category_to_apply)
# Notify everything/one that is to notify about changes.
global_stack.containersChanged.emit(global_stack.getTop())
def _stripFileToId(self, file):
@staticmethod
def _stripFileToId(file):
mime_type = MimeTypeDatabase.getMimeTypeForFile(file)
file = mime_type.stripExtension(file)
return file.replace("Cura/", "")
@ -1013,7 +1056,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return self._container_registry.getContainerForMimeType(MimeTypeDatabase.getMimeType("application/x-ultimaker-material-profile"))
## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data.
def _getContainerIdListFromSerialized(self, serialized):
@staticmethod
def _getContainerIdListFromSerialized(serialized):
parser = ConfigParser(interpolation = None, empty_lines_in_values = False)
parser.read_string(serialized)
@ -1034,12 +1078,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return container_ids
def _getMachineNameFromSerializedStack(self, serialized):
@staticmethod
def _getMachineNameFromSerializedStack(serialized):
parser = ConfigParser(interpolation = None, empty_lines_in_values = False)
parser.read_string(serialized)
return parser["general"].get("name", "")
def _getMaterialLabelFromSerialized(self, serialized):
@staticmethod
def _getMetaDataDictFromSerializedStack(serialized: str) -> Dict[str, str]:
parser = ConfigParser(interpolation = None, empty_lines_in_values = False)
parser.read_string(serialized)
return dict(parser["metadata"])
@staticmethod
def _getMaterialLabelFromSerialized(serialized):
data = ET.fromstring(serialized)
metadata = data.iterfind("./um:metadata/um:name/um:label", {"um": "http://www.ultimaker.com/material"})
for entry in metadata:

View file

@ -41,8 +41,9 @@ class WorkspaceDialog(QObject):
self._num_user_settings = 0
self._active_mode = ""
self._quality_name = ""
self._num_settings_overriden_by_quality_changes = 0
self._num_settings_overridden_by_quality_changes = 0
self._quality_type = ""
self._intent_name = ""
self._machine_name = ""
self._machine_type = ""
self._variant_type = ""
@ -60,6 +61,7 @@ class WorkspaceDialog(QObject):
hasVisibleSettingsFieldChanged = pyqtSignal()
numSettingsOverridenByQualityChangesChanged = pyqtSignal()
qualityTypeChanged = pyqtSignal()
intentNameChanged = pyqtSignal()
machineNameChanged = pyqtSignal()
materialLabelsChanged = pyqtSignal()
objectsOnPlateChanged = pyqtSignal()
@ -151,10 +153,10 @@ class WorkspaceDialog(QObject):
@pyqtProperty(int, notify=numSettingsOverridenByQualityChangesChanged)
def numSettingsOverridenByQualityChanges(self):
return self._num_settings_overriden_by_quality_changes
return self._num_settings_overridden_by_quality_changes
def setNumSettingsOverridenByQualityChanges(self, num_settings_overriden_by_quality_changes):
self._num_settings_overriden_by_quality_changes = num_settings_overriden_by_quality_changes
def setNumSettingsOverriddenByQualityChanges(self, num_settings_overridden_by_quality_changes):
self._num_settings_overridden_by_quality_changes = num_settings_overridden_by_quality_changes
self.numSettingsOverridenByQualityChangesChanged.emit()
@pyqtProperty(str, notify=qualityNameChanged)
@ -166,6 +168,15 @@ class WorkspaceDialog(QObject):
self._quality_name = quality_name
self.qualityNameChanged.emit()
@pyqtProperty(str, notify = intentNameChanged)
def intentName(self) -> str:
return self._intent_name
def setIntentName(self, intent_name: str) -> None:
if self._intent_name != intent_name:
self._intent_name = intent_name
self.intentNameChanged.emit()
@pyqtProperty(str, notify=activeModeChanged)
def activeMode(self):
return self._active_mode

View file

@ -1,10 +1,10 @@
// Copyright (c) 2016 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import UM 1.1 as UM
@ -13,8 +13,8 @@ UM.Dialog
id: base
title: catalog.i18nc("@title:window", "Open Project")
minimumWidth: 500 * screenScaleFactor
minimumHeight: 450 * screenScaleFactor
minimumWidth: UM.Theme.getSize("popup_dialog").width
minimumHeight: UM.Theme.getSize("popup_dialog").height
width: minimumWidth
height: minimumHeight
@ -24,7 +24,7 @@ UM.Dialog
onClosing: manager.notifyClosed()
onVisibleChanged:
{
if(visible)
if (visible)
{
machineResolveComboBox.currentIndex = 0
qualityChangesResolveComboBox.currentIndex = 0
@ -55,8 +55,8 @@ UM.Dialog
// See http://stackoverflow.com/questions/7659442/listelement-fields-as-properties
Component.onCompleted:
{
append({"key": "override", "label": catalog.i18nc("@action:ComboBox option", "Update existing")});
append({"key": "new", "label": catalog.i18nc("@action:ComboBox option", "Create new")});
append({"key": "override", "label": catalog.i18nc("@action:ComboBox Update/override existing profile", "Update existing")});
append({"key": "new", "label": catalog.i18nc("@action:ComboBox Save settings in a new profile", "Create new")});
}
}
@ -223,6 +223,21 @@ UM.Dialog
}
}
Row
{
width: parent.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@action:label", "Intent")
width: (parent.width / 3) | 0
}
Label
{
text: manager.intentName
width: (parent.width / 3) | 0
}
}
Row
{
width: parent.width
height: manager.numUserSettings != 0 ? childrenRect.height : 0

View file

@ -12,7 +12,6 @@ except ImportError:
from . import ThreeMFWorkspaceReader
from UM.i18n import i18nCatalog
from UM.Platform import Platform
catalog = i18nCatalog("cura")

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides support for reading 3MF files.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides support for writing 3MF files.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -0,0 +1,173 @@
# Copyright (c) 2019 fieldOfView, Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# This AMF parser is based on the AMF parser in legacy cura:
# https://github.com/daid/LegacyCura/blob/ad7641e059048c7dcb25da1f47c0a7e95e7f4f7c/Cura/util/meshLoaders/amf.py
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.CuraApplication import CuraApplication
from UM.Logger import Logger
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices
from UM.Mesh.MeshReader import MeshReader
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from UM.Scene.GroupDecorator import GroupDecorator
import numpy
import trimesh
import os.path
import zipfile
MYPY = False
try:
if not MYPY:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
from typing import Dict
class AMFReader(MeshReader):
def __init__(self) -> None:
super().__init__()
self._supported_extensions = [".amf"]
self._namespaces = {} # type: Dict[str, str]
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-amf",
comment = "AMF",
suffixes = ["amf"]
)
)
# Main entry point
# Reads the file, returns a SceneNode (possibly with nested ones), or None
def _read(self, file_name):
base_name = os.path.basename(file_name)
try:
zipped_file = zipfile.ZipFile(file_name)
xml_document = zipped_file.read(zipped_file.namelist()[0])
zipped_file.close()
except zipfile.BadZipfile:
raw_file = open(file_name, "r")
xml_document = raw_file.read()
raw_file.close()
try:
amf_document = ET.fromstring(xml_document)
except ET.ParseError:
Logger.log("e", "Could not parse XML in file %s" % base_name)
return None
if "unit" in amf_document.attrib:
unit = amf_document.attrib["unit"].lower()
else:
unit = "millimeter"
if unit == "millimeter":
scale = 1.0
elif unit == "meter":
scale = 1000.0
elif unit == "inch":
scale = 25.4
elif unit == "feet":
scale = 304.8
elif unit == "micron":
scale = 0.001
else:
Logger.log("w", "Unknown unit in amf: %s. Using mm instead." % unit)
scale = 1.0
nodes = []
for amf_object in amf_document.iter("object"):
for amf_mesh in amf_object.iter("mesh"):
amf_mesh_vertices = []
for vertices in amf_mesh.iter("vertices"):
for vertex in vertices.iter("vertex"):
for coordinates in vertex.iter("coordinates"):
v = [0.0, 0.0, 0.0]
for t in coordinates:
if t.tag == "x":
v[0] = float(t.text) * scale
elif t.tag == "y":
v[2] = -float(t.text) * scale
elif t.tag == "z":
v[1] = float(t.text) * scale
amf_mesh_vertices.append(v)
if not amf_mesh_vertices:
continue
indices = []
for volume in amf_mesh.iter("volume"):
for triangle in volume.iter("triangle"):
f = [0, 0, 0]
for t in triangle:
if t.tag == "v1":
f[0] = int(t.text)
elif t.tag == "v2":
f[1] = int(t.text)
elif t.tag == "v3":
f[2] = int(t.text)
indices.append(f)
mesh = trimesh.base.Trimesh(vertices = numpy.array(amf_mesh_vertices, dtype = numpy.float32), faces = numpy.array(indices, dtype = numpy.int32))
mesh.merge_vertices()
mesh.remove_unreferenced_vertices()
mesh.fix_normals()
mesh_data = self._toMeshData(mesh)
new_node = CuraSceneNode()
new_node.setSelectable(True)
new_node.setMeshData(mesh_data)
new_node.setName(base_name if len(nodes) == 0 else "%s %d" % (base_name, len(nodes)))
new_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
new_node.addDecorator(SliceableObjectDecorator())
nodes.append(new_node)
if not nodes:
Logger.log("e", "No meshes in file %s" % base_name)
return None
if len(nodes) == 1:
return nodes[0]
# Add all scenenodes to a group so they stay together
group_node = CuraSceneNode()
group_node.addDecorator(GroupDecorator())
group_node.addDecorator(ConvexHullDecorator())
group_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
for node in nodes:
node.setParent(group_node)
return group_node
def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
tri_faces = tri_node.faces
tri_vertices = tri_node.vertices
indices = []
vertices = []
index_count = 0
face_count = 0
for tri_face in tri_faces:
face = []
for tri_index in tri_face:
vertices.append(tri_vertices[tri_index])
face.append(index_count)
index_count += 1
indices.append(face)
face_count += 1
vertices = numpy.asarray(vertices, dtype = numpy.float32)
indices = numpy.asarray(indices, dtype = numpy.int32)
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals)
return mesh_data

View file

@ -0,0 +1,21 @@
# Copyright (c) 2019 fieldOfView
# Cura is released under the terms of the LGPLv3 or higher.
from . import AMFReader
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")
def getMetaData():
return {
"mesh_reader": [
{
"extension": "amf",
"description": i18n_catalog.i18nc("@item:inlistbox", "AMF File")
}
]
}
def register(app):
return {"mesh_reader": AMFReader.AMFReader()}

View file

@ -0,0 +1,7 @@
{
"name": "AMF Reader",
"author": "fieldOfView",
"version": "1.0.0",
"description": "Provides support for reading AMF files.",
"api": "7.0.0"
}

View file

@ -1,109 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog
from UM.Extension import Extension
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Version import Version
from PyQt5.QtCore import pyqtSlot, QObject
import os.path
import collections
catalog = i18nCatalog("cura")
class ChangeLog(Extension, QObject,):
def __init__(self, parent = None):
QObject.__init__(self, parent)
Extension.__init__(self)
self._changelog_window = None
self._changelog_context = None
version_string = Application.getInstance().getVersion()
if version_string is not "master":
self._current_app_version = Version(version_string)
else:
self._current_app_version = None
self._change_logs = None
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
self.setMenuName(catalog.i18nc("@item:inmenu", "Changelog"))
self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
def getChangeLogs(self):
if not self._change_logs:
self.loadChangeLogs()
return self._change_logs
@pyqtSlot(result = str)
def getChangeLogString(self):
logs = self.getChangeLogs()
result = ""
for version in logs:
result += "<h1>" + str(version) + "</h1><br>"
result += ""
for change in logs[version]:
if str(change) != "":
result += "<b>" + str(change) + "</b><br>"
for line in logs[version][change]:
result += str(line) + "<br>"
result += "<br>"
pass
return result
def loadChangeLogs(self):
self._change_logs = collections.OrderedDict()
with open(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.txt"), "r", encoding = "utf-8") as f:
open_version = None
open_header = "" # Initialise to an empty header in case there is no "*" in the first line of the changelog
for line in f:
line = line.replace("\n","")
if "[" in line and "]" in line:
line = line.replace("[","")
line = line.replace("]","")
open_version = Version(line)
open_header = ""
self._change_logs[open_version] = collections.OrderedDict()
elif line.startswith("*"):
open_header = line.replace("*","")
self._change_logs[open_version][open_header] = []
elif line != "":
if open_header not in self._change_logs[open_version]:
self._change_logs[open_version][open_header] = []
self._change_logs[open_version][open_header].append(line)
def _onEngineCreated(self):
if not self._current_app_version:
return #We're on dev branch.
if Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown") == "master":
latest_version_shown = Version("0.0.0")
else:
latest_version_shown = Version(Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown"))
Application.getInstance().getPreferences().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
# Do not show the changelog when there is no global container stack
# This implies we are running Cura for the first time.
if not Application.getInstance().getGlobalContainerStack():
return
if self._current_app_version > latest_version_shown:
self.showChangelog()
def showChangelog(self):
if not self._changelog_window:
self.createChangelogWindow()
self._changelog_window.show()
def hideChangelog(self):
if self._changelog_window:
self._changelog_window.hide()
def createChangelogWindow(self):
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml")
self._changelog_window = Application.getInstance().createQmlComponent(path, {"manager": self})

View file

@ -1,41 +0,0 @@
// Copyright (c) 2015 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import UM 1.1 as UM
UM.Dialog
{
id: base
minimumWidth: (UM.Theme.getSize("modal_window_minimum").width * 0.75) | 0
minimumHeight: (UM.Theme.getSize("modal_window_minimum").height * 0.75) | 0
width: minimumWidth
height: minimumHeight
title: catalog.i18nc("@label", "Changelog")
TextArea
{
anchors.fill: parent
text: manager.getChangeLogString()
readOnly: true;
textFormat: TextEdit.RichText
}
rightButtons: [
Button
{
UM.I18nCatalog
{
id: catalog
name: "cura"
}
text: catalog.i18nc("@action:button", "Close")
onClicked: base.hide()
}
]
}

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,8 +0,0 @@
{
"name": "Changelog",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Shows changes since latest checked version.",
"api": "6.0",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"description": "Backup and restore your configuration.",
"version": "1.2.0",
"api": 6,
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -45,7 +45,7 @@ class DriveApiService:
"Authorization": "Bearer {}".format(access_token)
})
except requests.exceptions.ConnectionError:
Logger.log("w", "Unable to connect with the server.")
Logger.logException("w", "Unable to connect with the server.")
return []
# HTTP status 300s mean redirection. 400s and 500s are errors.
@ -54,7 +54,13 @@ class DriveApiService:
Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text)
Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = catalog.i18nc("@info:title", "Backup")).show()
return []
return backup_list_request.json()["data"]
backup_list_response = backup_list_request.json()
if "data" not in backup_list_response:
Logger.log("w", "Could not get backups from remote, actual response body was: %s", str(backup_list_response))
return []
return backup_list_response["data"]
def createBackup(self) -> None:
self.creatingStateChanged.emit(is_creating = True)
@ -92,7 +98,12 @@ class DriveApiService:
# If there is no download URL, we can't restore the backup.
return self._emitRestoreError()
download_package = requests.get(download_url, stream = True)
try:
download_package = requests.get(download_url, stream = True)
except requests.exceptions.ConnectionError:
Logger.logException("e", "Unable to connect with the server")
return self._emitRestoreError()
if download_package.status_code >= 300:
# Something went wrong when attempting to download the backup.
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
@ -136,9 +147,14 @@ class DriveApiService:
Logger.log("w", "Could not get access token.")
return False
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
"Authorization": "Bearer {}".format(access_token)
})
try:
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
"Authorization": "Bearer {}".format(access_token)
})
except requests.exceptions.ConnectionError:
Logger.logException("e", "Unable to connect with the server")
return False
if delete_backup.status_code >= 300:
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
return False
@ -153,15 +169,19 @@ class DriveApiService:
if not access_token:
Logger.log("w", "Could not get access token.")
return None
backup_upload_request = requests.put(self.BACKUP_URL, json = {
"data": {
"backup_size": backup_size,
"metadata": backup_metadata
}
}, headers = {
"Authorization": "Bearer {}".format(access_token)
})
try:
backup_upload_request = requests.put(
self.BACKUP_URL,
json = {"data": {"backup_size": backup_size,
"metadata": backup_metadata
}
},
headers = {
"Authorization": "Bearer {}".format(access_token)
})
except requests.exceptions.ConnectionError:
Logger.logException("e", "Unable to connect with the server")
return None
# Any status code of 300 or above indicates an error.
if backup_upload_request.status_code >= 300:

View file

@ -18,6 +18,7 @@ Window
minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height)
maximumWidth: Math.round(minimumWidth * 1.2)
maximumHeight: Math.round(minimumHeight * 1.2)
modality: Qt.ApplicationModal
width: minimumWidth
height: minimumHeight
color: UM.Theme.getColor("main_background")

View file

@ -15,14 +15,12 @@ from UM.Signal import Signal
from UM.Logger import Logger
from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
from UM.Platform import Platform
from UM.Qt.Duration import DurationFormat
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.SettingInstance import SettingInstance #For typing.
from UM.Tool import Tool #For typing.
from UM.Mesh.MeshData import MeshData #For typing.
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
@ -209,7 +207,7 @@ class CuraEngineBackend(QObject, Backend):
self._createSocket()
if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon.
Logger.log("d", "Aborting process layers job...")
Logger.log("i", "Aborting process layers job...")
self._process_layers_job.abort()
self._process_layers_job = None
@ -224,7 +222,7 @@ class CuraEngineBackend(QObject, Backend):
## Perform a slice of the scene.
def slice(self) -> None:
Logger.log("d", "Starting to slice...")
Logger.log("i", "Starting to slice...")
self._slice_start_time = time()
if not self._build_plates_to_be_sliced:
self.processingProgress.emit(1.0)
@ -371,7 +369,7 @@ class CuraEngineBackend(QObject, Backend):
elif job.getResult() == StartJobResult.ObjectSettingError:
errors = {}
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()):
stack = node.callDecoration("getStack")
if not stack:
continue
@ -402,7 +400,7 @@ class CuraEngineBackend(QObject, Backend):
self.setState(BackendState.NotStarted)
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s.") % job.getMessage(),
title = catalog.i18nc("@info:title", "Unable to slice"))
self._error_message.show()
self.setState(BackendState.Error)
@ -440,7 +438,7 @@ class CuraEngineBackend(QObject, Backend):
if not self._application.getPreferences().getValue("general/auto_slice"):
enable_timer = False
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isBlockSlicing"):
enable_timer = False
self.setState(BackendState.Disabled)
@ -462,7 +460,7 @@ class CuraEngineBackend(QObject, Backend):
## Return a dict with number of objects per build plate
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
num_objects = defaultdict(int) #type: Dict[int, int]
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
for node in DepthFirstIterator(self._scene.getRoot()):
# Only count sliceable objects
if node.callDecoration("isSliceable"):
build_plate_number = node.callDecoration("getBuildPlateNumber")
@ -476,7 +474,7 @@ class CuraEngineBackend(QObject, Backend):
#
# \param source The scene node that was changed.
def _onSceneChanged(self, source: SceneNode) -> None:
if not isinstance(source, SceneNode):
if not source.callDecoration("isSliceable"):
return
# This case checks if the source node is a node that contains GCode. In this case the
@ -519,9 +517,6 @@ class CuraEngineBackend(QObject, Backend):
self._build_plates_to_be_sliced.append(build_plate_number)
self.printDurationMessage.emit(source_build_plate_number, {}, [])
self.processingProgress.emit(0.0)
self.setState(BackendState.NotStarted)
# if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData(build_plate_changed)
self._invokeSlice()
@ -548,15 +543,25 @@ class CuraEngineBackend(QObject, Backend):
if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
self._start_slice_job.setIsCancelled(False)
# Check if there's any slicable object in the scene.
def hasSlicableObject(self) -> bool:
has_slicable = False
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isSliceable"):
has_slicable = True
break
return has_slicable
## Remove old layer data (if any)
def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
# Clear out any old gcode
self._scene.gcode_dict = {} # type: ignore
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData"):
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
node.getParent().removeChild(node)
# We can asume that all nodes have a parent as we're looping through the scene (and filter out root)
cast(SceneNode, node.getParent()).removeChild(node)
def markSliceAll(self) -> None:
for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1):
@ -565,10 +570,14 @@ class CuraEngineBackend(QObject, Backend):
## Convenient function: mark everything to slice, emit state and clear layer data
def needsSlicing(self) -> None:
# CURA-6604: If there's no slicable object, do not (try to) trigger slice, which will clear all the current
# gcode. This can break Gcode file loading if it tries to remove it afterwards.
if not self.hasSlicableObject():
return
self.determineAutoSlicing()
self.stopSlicing()
self.markSliceAll()
self.processingProgress.emit(0.0)
self.setState(BackendState.NotStarted)
if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData()
@ -636,7 +645,10 @@ class CuraEngineBackend(QObject, Backend):
self.setState(BackendState.Done)
self.processingProgress.emit(1.0)
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
try:
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
gcode_list = []
for index, line in enumerate(gcode_list):
replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths))
@ -675,14 +687,20 @@ class CuraEngineBackend(QObject, Backend):
#
# \param message The protobuf message containing g-code, encoded as UTF-8.
def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None:
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
try:
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
pass # Throw the message away.
## Called when a g-code prefix message is received from the engine.
#
# \param message The protobuf message containing the g-code prefix,
# encoded as UTF-8.
def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
try:
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
pass # Throw the message away.
## Creates a new socket connection.
def _createSocket(self, protocol_file: str = None) -> None:
@ -737,6 +755,7 @@ class CuraEngineBackend(QObject, Backend):
"support_interface": message.time_support_interface,
"support": message.time_support,
"skirt": message.time_skirt,
"prime_tower": message.time_prime_tower,
"travel": message.time_travel,
"retract": message.time_retract,
"none": message.time_none
@ -815,9 +834,8 @@ class CuraEngineBackend(QObject, Backend):
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack.containersChanged.disconnect(self._onChanged)
extruders = list(self._global_container_stack.extruders.values())
for extruder in extruders:
for extruder in self._global_container_stack.extruderList:
extruder.propertyChanged.disconnect(self._onSettingChanged)
extruder.containersChanged.disconnect(self._onChanged)
@ -826,8 +844,8 @@ class CuraEngineBackend(QObject, Backend):
if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
self._global_container_stack.containersChanged.connect(self._onChanged)
extruders = list(self._global_container_stack.extruders.values())
for extruder in extruders:
for extruder in self._global_container_stack.extruderList:
extruder.propertyChanged.connect(self._onSettingChanged)
extruder.containersChanged.connect(self._onChanged)
self._onChanged()

View file

@ -1,4 +1,4 @@
#Copyright (c) 2017 Ultimaker B.V.
#Copyright (c) 2019 Ultimaker B.V.
#Cura is released under the terms of the LGPLv3 or higher.
import gc
@ -24,7 +24,7 @@ from cura import LayerPolygon
import numpy
from time import time
from cura.Settings.ExtrudersModel import ExtrudersModel
from cura.Machines.Models.ExtrudersModel import ExtrudersModel
catalog = i18nCatalog("cura")
@ -136,23 +136,23 @@ class ProcessSlicedLayersJob(Job):
extruder = polygon.extruder
line_types = numpy.fromstring(polygon.line_type, dtype="u1") # Convert bytearray to numpy array
line_types = numpy.fromstring(polygon.line_type, dtype = "u1") # Convert bytearray to numpy array
line_types = line_types.reshape((-1,1))
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
points = numpy.fromstring(polygon.points, dtype = "f4") # Convert bytearray to numpy array
if polygon.point_type == 0: # Point2D
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
else: # Point3D
points = points.reshape((-1,3))
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
line_widths = numpy.fromstring(polygon.line_width, dtype = "f4") # Convert bytearray to numpy array
line_widths = line_widths.reshape((-1,1)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
line_thicknesses = numpy.fromstring(polygon.line_thickness, dtype="f4") # Convert bytearray to numpy array
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 = 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.
@ -194,7 +194,7 @@ class ProcessSlicedLayersJob(Job):
manager = ExtruderManager.getInstance()
extruders = manager.getActiveExtruderStacks()
if extruders:
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
material_color_map = numpy.zeros((len(extruders), 4), dtype = numpy.float32)
for extruder in extruders:
position = int(extruder.getMetaDataEntry("position", default = "0"))
try:
@ -206,8 +206,8 @@ class ProcessSlicedLayersJob(Job):
material_color_map[position, :] = color
else:
# Single extruder via global stack.
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
color_code = global_container_stack.material.getMetaDataEntry("color_code", default="#e0e000")
material_color_map = numpy.zeros((1, 4), dtype = numpy.float32)
color_code = global_container_stack.material.getMetaDataEntry("color_code", default = "#e0e000")
color = colorCodeToRGBA(color_code)
material_color_map[0, :] = color

View file

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import numpy
@ -11,6 +11,7 @@ import Arcus #For typing.
from UM.Job import Job
from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode
from UM.Settings.ContainerStack import ContainerStack #For typing.
from UM.Settings.SettingRelation import SettingRelation #For typing.
@ -71,7 +72,7 @@ class GcodeStartEndFormatter(Formatter):
value = default_value_str
# "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value.
if key in kwargs["-1"]:
value = kwargs["-1"]
value = kwargs["-1"][key]
if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]:
value = kwargs[str(extruder_nr)][key]
@ -105,9 +106,14 @@ class StartSliceJob(Job):
if stack is None:
return False
# if there are no per-object settings we don't need to check the other settings here
stack_top = stack.getTop()
if stack_top is None or not stack_top.getAllKeys():
return False
for key in stack.getAllKeys():
validation_state = stack.getProperty(key, "validationState")
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError, ValidatorState.Invalid):
Logger.log("w", "Setting %s is not valid, but %s. Aborting slicing.", key, validation_state)
return True
Job.yieldThread()
@ -133,6 +139,14 @@ class StartSliceJob(Job):
self.setResult(StartJobResult.BuildPlateError)
return
# Wait for error checker to be done.
while CuraApplication.getInstance().getMachineErrorChecker().needToWaitForResult:
time.sleep(0.1)
if CuraApplication.getInstance().getMachineErrorChecker().hasError:
self.setResult(StartJobResult.SettingError)
return
# Don't slice if the buildplate or the nozzle type is incompatible with the materials
if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \
not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable:
@ -150,7 +164,7 @@ class StartSliceJob(Job):
# Don't slice if there is a per object setting with an error value.
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
for node in DepthFirstIterator(self._scene.getRoot()):
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
continue
@ -160,15 +174,16 @@ class StartSliceJob(Job):
with self._scene.getSceneLock():
# Remove old layer data.
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
node.getParent().removeChild(node)
# Singe we walk through all nodes in the scene, they always have a parent.
cast(SceneNode, node.getParent()).removeChild(node)
break
# Get the objects in their groups to print.
object_groups = []
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
for node in OneAtATimeIterator(self._scene.getRoot()):
temp_list = []
# Node can't be printed, so don't bother sending it.
@ -183,7 +198,8 @@ class StartSliceJob(Job):
children = node.getAllChildren()
children.append(node)
for child_node in children:
if child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
mesh_data = child_node.getMeshData()
if mesh_data and mesh_data.getVertices() is not None:
temp_list.append(child_node)
if temp_list:
@ -194,12 +210,10 @@ class StartSliceJob(Job):
else:
temp_list = []
has_printing_mesh = False
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None:
per_object_stack = node.callDecoration("getStack")
is_non_printing_mesh = False
if per_object_stack:
is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
for node in DepthFirstIterator(self._scene.getRoot()):
mesh_data = node.getMeshData()
if node.callDecoration("isSliceable") and mesh_data and mesh_data.getVertices() is not None:
is_non_printing_mesh = bool(node.callDecoration("isNonPrintingMesh"))
# Find a reason not to add the node
if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
@ -213,7 +227,7 @@ class StartSliceJob(Job):
Job.yieldThread()
#If the list doesn't have any model with suitable settings then clean the list
# If the list doesn't have any model with suitable settings then clean the list
# otherwise CuraEngine will crash
if not has_printing_mesh:
temp_list.clear()
@ -259,18 +273,19 @@ class StartSliceJob(Job):
self._buildGlobalInheritsStackMessage(stack)
# Build messages for extruder stacks
# Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first,
# then CuraEngine can slice with the wrong settings. This I think should be fixed in CuraEngine as well.
extruder_stack_list = sorted(list(global_stack.extruders.items()), key = lambda item: int(item[0]))
for _, extruder_stack in extruder_stack_list:
for extruder_stack in global_stack.extruderList:
self._buildExtruderMessage(extruder_stack)
for group in filtered_object_groups:
group_message = self._slice_message.addRepeatedMessage("object_lists")
if group[0].getParent() is not None and group[0].getParent().callDecoration("isGroup"):
self._handlePerObjectSettings(group[0].getParent(), group_message)
parent = group[0].getParent()
if parent is not None and parent.callDecoration("isGroup"):
self._handlePerObjectSettings(cast(CuraSceneNode, parent), group_message)
for object in group:
mesh_data = object.getMeshData()
if mesh_data is None:
continue
rot_scale = object.getWorldTransformation().getTransposed().getData()[0:3, 0:3]
translate = object.getWorldTransformation().getData()[:3, 3]
@ -294,7 +309,7 @@ class StartSliceJob(Job):
obj.vertices = flat_verts
self._handlePerObjectSettings(object, obj)
self._handlePerObjectSettings(cast(CuraSceneNode, object), obj)
Job.yieldThread()
@ -323,9 +338,10 @@ class StartSliceJob(Job):
value = stack.getProperty(key, "value")
result[key] = value
Job.yieldThread()
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
result["print_temperature"] = result["material_print_temperature"]
result["travel_speed"] = result["speed_travel"]
result["time"] = time.strftime("%H:%M:%S") #Some extra settings.
result["date"] = time.strftime("%d-%m-%Y")
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
@ -336,25 +352,29 @@ class StartSliceJob(Job):
return result
def _cacheAllExtruderSettings(self):
global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack())
# NB: keys must be strings for the string formatter
self._all_extruders_settings = {
"-1": self._buildReplacementTokens(global_stack)
}
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
## Replace setting tokens in a piece of g-code.
# \param value A piece of g-code to replace tokens in.
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
if not self._all_extruders_settings:
global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack())
# NB: keys must be strings for the string formatter
self._all_extruders_settings = {
"-1": self._buildReplacementTokens(global_stack)
}
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
self._cacheAllExtruderSettings()
try:
# any setting can be used as a token
fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr)
if self._all_extruders_settings is None:
return ""
settings = self._all_extruders_settings.copy()
settings["default_extruder_nr"] = default_extruder_nr
return str(fmt.format(value, **settings))
@ -366,8 +386,14 @@ class StartSliceJob(Job):
def _buildExtruderMessage(self, stack: ContainerStack) -> None:
message = self._slice_message.addRepeatedMessage("extruders")
message.id = int(stack.getMetaDataEntry("position"))
if not self._all_extruders_settings:
self._cacheAllExtruderSettings()
settings = self._buildReplacementTokens(stack)
if self._all_extruders_settings is None:
return
extruder_nr = stack.getProperty("extruder_nr", "value")
settings = self._all_extruders_settings[str(extruder_nr)].copy()
# Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")
@ -391,7 +417,13 @@ class StartSliceJob(Job):
# The settings are taken from the global stack. This does not include any
# per-extruder settings or per-object settings.
def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None:
settings = self._buildReplacementTokens(stack)
if not self._all_extruders_settings:
self._cacheAllExtruderSettings()
if self._all_extruders_settings is None:
return
settings = self._all_extruders_settings["-1"].copy()
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
start_gcode = settings["machine_start_gcode"]

View file

@ -2,7 +2,7 @@
"name": "CuraEngine Backend",
"author": "Ultimaker B.V.",
"description": "Provides the link to the CuraEngine slicing backend.",
"api": "6.0",
"api": "7.0",
"version": "1.0.1",
"i18n-catalog": "cura"
}

View file

@ -1,11 +1,14 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser
from UM.PluginRegistry import PluginRegistry
import configparser
from typing import List, Optional, Tuple
from UM.Logger import Logger
from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
from cura.CuraApplication import CuraApplication
from cura.Machines.ContainerTree import ContainerTree
from cura.ReaderWriters.ProfileReader import ProfileReader
import zipfile
@ -17,39 +20,43 @@ import zipfile
class CuraProfileReader(ProfileReader):
## Initialises the cura profile reader.
# This does nothing since the only other function is basically stateless.
def __init__(self):
def __init__(self) -> None:
super().__init__()
## Reads a cura profile from a file and returns it.
#
# \param file_name The file to read the cura profile from.
# \return The cura profile that was in the file, if any. If the file could
# not be read or didn't contain a valid profile, \code None \endcode is
# \return The cura profiles that were in the file, if any. If the file
# could not be read or didn't contain a valid profile, ``None`` is
# returned.
def read(self, file_name):
def read(self, file_name: str) -> List[Optional[InstanceContainer]]:
try:
with zipfile.ZipFile(file_name, "r") as archive:
results = []
results = [] # type: List[Optional[InstanceContainer]]
for profile_id in archive.namelist():
with archive.open(profile_id) as f:
serialized = f.read()
profile = self._loadProfile(serialized.decode("utf-8"), profile_id)
if profile is not None:
results.append(profile)
upgraded_profiles = self._upgradeProfile(serialized.decode("utf-8"), profile_id) #After upgrading it may split into multiple profiles.
for upgraded_profile in upgraded_profiles:
serialization, new_id = upgraded_profile
profile = self._loadProfile(serialization, new_id)
if profile is not None:
results.append(profile)
return results
except zipfile.BadZipFile:
# It must be an older profile from Cura 2.1.
with open(file_name, encoding = "utf-8") as fhandle:
serialized = fhandle.read()
return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized, file_name)]
serialized_bytes = fhandle.read()
return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized_bytes, file_name)]
## Convert a profile from an old Cura to this Cura if needed.
#
# \param serialized \type{str} The profile data to convert in the serialized on-disk format.
# \param profile_id \type{str} The name of the profile.
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
def _upgradeProfile(self, serialized, profile_id):
# \param serialized The profile data to convert in the serialized on-disk
# format.
# \param profile_id The name of the profile.
# \return List of serialized profile strings and matching profile names.
def _upgradeProfile(self, serialized: str, profile_id: str) -> List[Tuple[str, str]]:
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
@ -61,48 +68,67 @@ class CuraProfileReader(ProfileReader):
return []
version = int(parser["general"]["version"])
setting_version = int(parser["metadata"].get("setting_version", "0"))
if InstanceContainer.Version != version:
name = parser["general"]["name"]
return self._upgradeProfileVersion(serialized, name, version)
return self._upgradeProfileVersion(serialized, name, version, setting_version)
else:
return [(serialized, profile_id)]
## Load a profile from a serialized string.
#
# \param serialized \type{str} The profile data to read.
# \param profile_id \type{str} The name of the profile.
# \return \type{InstanceContainer|None}
def _loadProfile(self, serialized, profile_id):
# \param serialized The profile data to read.
# \param profile_id The name of the profile.
# \return The profile that was stored in the string.
def _loadProfile(self, serialized: str, profile_id: str) -> Optional[InstanceContainer]:
# Create an empty profile.
profile = InstanceContainer(profile_id)
profile.setMetaDataEntry("type", "quality_changes")
try:
profile.deserialize(serialized)
profile.deserialize(serialized, file_name = profile_id)
except ContainerFormatError as e:
Logger.log("e", "Error in the format of a container: %s", str(e))
return None
except Exception as e:
Logger.log("e", "Error while trying to parse profile: %s", str(e))
return None
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
return None
active_quality_definition = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
if profile.getMetaDataEntry("definition") != active_quality_definition:
profile.setMetaDataEntry("definition", active_quality_definition)
return profile
## Upgrade a serialized profile to the current profile format.
#
# \param serialized \type{str} The profile data to convert.
# \param profile_id \type{str} The name of the profile.
# \param source_version \type{int} The profile version of 'serialized'.
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
def _upgradeProfileVersion(self, serialized, profile_id, source_version):
converter_plugins = PluginRegistry.getInstance().getAllMetaData(filter={"version_upgrade": {} }, active_only=True)
# \param serialized The profile data to convert.
# \param profile_id The name of the profile.
# \param source_version The profile version of 'serialized'.
# \return List of serialized profile strings and matching profile names.
def _upgradeProfileVersion(self, serialized: str, profile_id: str, main_version: int, setting_version: int) -> List[Tuple[str, str]]:
source_version = main_version * 1000000 + setting_version
source_format = ("profile", source_version)
profile_convert_funcs = [plugin["version_upgrade"][source_format][2] for plugin in converter_plugins
if source_format in plugin["version_upgrade"] and plugin["version_upgrade"][source_format][1] == InstanceContainer.Version]
if not profile_convert_funcs:
from UM.VersionUpgradeManager import VersionUpgradeManager
results = VersionUpgradeManager.getInstance().updateFilesData("quality_changes", source_version, [serialized], [profile_id])
if results is None:
return []
filenames, outputs = profile_convert_funcs[0](serialized, profile_id)
if filenames is None and outputs is None:
serialized = results.files_data[0]
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
if "general" not in parser:
Logger.log("w", "Missing required section 'general'.")
return []
return list(zip(outputs, filenames))
new_source_version = results.version
if int(new_source_version / 1000000) != InstanceContainer.Version or new_source_version % 1000000 != CuraApplication.SettingVersion:
Logger.log("e", "Failed to upgrade profile [%s]", profile_id)
if int(parser["general"]["version"]) != InstanceContainer.Version:
Logger.log("e", "Failed to upgrade profile [%s]", profile_id)
return []
return [(serialized, profile_id)]

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides support for importing Cura profiles.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides support for exporting Cura profiles.",
"api": "6.0",
"api": "7.0",
"i18n-catalog":"cura"
}

View file

@ -10,6 +10,9 @@ from UM.Version import Version
import urllib.request
from urllib.error import URLError
from typing import Dict, Optional
import ssl
import certifi
from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine
from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage
@ -39,8 +42,12 @@ class FirmwareUpdateCheckerJob(Job):
result = self.STRING_ZERO_VERSION
try:
# CURA-6698 Create an SSL context and use certifi CA certificates for verification.
context = ssl.SSLContext(protocol = ssl.PROTOCOL_TLSv1_2)
context.load_verify_locations(cafile = certifi.where())
request = urllib.request.Request(url, headers = self._headers)
response = urllib.request.urlopen(request)
response = urllib.request.urlopen(request, context = context)
result = response.read().decode("utf-8")
except URLError:
Logger.log("w", "Could not reach '{0}', if this URL is old, consider removal.".format(url))
@ -104,7 +111,7 @@ class FirmwareUpdateCheckerJob(Job):
# because the new version of Cura will be release before the firmware and we don't want to
# 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")
Logger.log("i", "Showing firmware update message for new version: {version}".format(version = current_version))
message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name,
self._lookups.getRedirectUserUrl())
message.actionTriggered.connect(self._callback)
@ -113,7 +120,7 @@ class FirmwareUpdateCheckerJob(Job):
Logger.log("i", "No machine with name {0} in list of firmware to check.".format(self._machine_name))
except Exception as e:
Logger.log("w", "Failed to check for new version: %s", e)
Logger.logException("w", "Failed to check for new version: %s", e)
if not self.silent:
Message(i18n_catalog.i18nc("@info", "Could not access update information.")).show()
return

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Checks for firmware updates.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides a machine actions for updating firmware.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -27,6 +27,6 @@ class GCodeGzReader(MeshReader):
file_data = file.read()
uncompressed_gcode = gzip.decompress(file_data).decode("utf-8")
PluginRegistry.getInstance().getPluginObject("GCodeReader").preReadFromStream(uncompressed_gcode)
result = PluginRegistry.getInstance().getPluginObject("GCodeReader").readFromStream(uncompressed_gcode)
result = PluginRegistry.getInstance().getPluginObject("GCodeReader").readFromStream(uncompressed_gcode, file_name)
return result

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Reads g-code from a compressed archive.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Writes g-code to a compressed archive.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides support for importing profiles from g-code files.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -1,31 +1,33 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import math
import re
from typing import Dict, List, NamedTuple, Optional, Union, Set
import numpy
from UM.Backend import Backend
from UM.Job import Job
from UM.Logger import Logger
from UM.Math.Vector import Vector
from UM.Message import Message
from cura.Scene.CuraSceneNode import CuraSceneNode
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
from cura.CuraApplication import CuraApplication
from cura.LayerDataBuilder import LayerDataBuilder
from cura.LayerDataDecorator import LayerDataDecorator
from cura.LayerPolygon import LayerPolygon
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.GCodeListDecorator import GCodeListDecorator
from cura.Settings.ExtruderManager import ExtruderManager
import numpy
import math
import re
from typing import Dict, List, NamedTuple, Optional, Union
catalog = i18nCatalog("cura")
PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])])
## This parser is intended to interpret the common firmware codes among all the
# different flavors
class FlavorParser:
@ -33,9 +35,11 @@ class FlavorParser:
def __init__(self) -> None:
CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
self._cancelled = False
self._message = None
self._message = None # type: Optional[Message]
self._layer_number = 0
self._extruder_number = 0
# All extruder numbers that have been seen
self._extruders_seen = {0} # type: Set[int]
self._clearValues()
self._scene_node = None
# X, Y, Z position, F feedrate and E extruder values are stored
@ -64,7 +68,7 @@ class FlavorParser:
if n < 0:
return None
n += len(code)
pattern = re.compile("[;\s]")
pattern = re.compile("[;\\s]")
match = pattern.search(line, n)
m = match.start() if match is not None else -1
try:
@ -290,7 +294,12 @@ class FlavorParser:
extruder.getProperty("machine_nozzle_offset_y", "value")]
return result
def processGCodeStream(self, stream: str) -> Optional[CuraSceneNode]:
#
# CURA-6643
# This function needs the filename so it can be set to the SceneNode. Otherwise, if you load a GCode file and press
# F5, that gcode SceneNode will be removed because it doesn't have a file to be reloaded from.
#
def processGCodeStream(self, stream: str, filename: str) -> Optional["CuraSceneNode"]:
Logger.log("d", "Preparing to load GCode")
self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths
@ -368,6 +377,8 @@ class FlavorParser:
self._layer_type = LayerPolygon.InfillType
elif type == "SUPPORT-INTERFACE":
self._layer_type = LayerPolygon.SupportInterfaceType
elif type == "PRIME-TOWER":
self._layer_type = LayerPolygon.PrimeTowerType
else:
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
@ -414,6 +425,7 @@ class FlavorParser:
if line.startswith("T"):
T = self._getInt(line, "T")
if T is not None:
self._extruders_seen.add(T)
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
current_path.clear()
@ -425,7 +437,8 @@ class FlavorParser:
if line.startswith("M"):
M = self._getInt(line, "M")
self.processMCode(M, line, current_position, current_path)
if M is not None:
self.processMCode(M, line, current_position, current_path)
# "Flush" leftovers. Last layer paths are still stored
if len(current_path) > 1:
@ -448,6 +461,7 @@ class FlavorParser:
scene_node.addDecorator(decorator)
gcode_list_decorator = GCodeListDecorator()
gcode_list_decorator.setGcodeFileName(filename)
gcode_list_decorator.setGCodeList(gcode_list)
scene_node.addDecorator(gcode_list_decorator)
@ -462,10 +476,9 @@ class FlavorParser:
if self._layer_number == 0:
Logger.log("w", "File doesn't contain any valid layers")
settings = CuraApplication.getInstance().getGlobalContainerStack()
if not settings.getProperty("machine_center_is_zero", "value"):
machine_width = settings.getProperty("machine_width", "value")
machine_depth = settings.getProperty("machine_depth", "value")
if not global_stack.getProperty("machine_center_is_zero", "value"):
machine_width = global_stack.getProperty("machine_width", "value")
machine_depth = global_stack.getProperty("machine_depth", "value")
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
Logger.log("d", "GCode loading finished")

View file

@ -2,6 +2,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Union, List, TYPE_CHECKING
from UM.FileHandler.FileReader import FileReader
from UM.Mesh.MeshReader import MeshReader
from UM.i18n import i18nCatalog
@ -9,10 +11,13 @@ from UM.Application import Application
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
catalog = i18nCatalog("cura")
from .FlavorParser import FlavorParser
from . import MarlinFlavorParser, RepRapFlavorParser
if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode
from cura.Scene.CuraSceneNode import CuraSceneNode
# Class for loading and parsing G-code files
@ -33,7 +38,7 @@ class GCodeReader(MeshReader):
)
self._supported_extensions = [".gcode", ".g"]
self._flavor_reader = None
self._flavor_reader = None # type: Optional[FlavorParser]
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
@ -57,10 +62,16 @@ class GCodeReader(MeshReader):
file_data = file.read()
return self.preReadFromStream(file_data, args, kwargs)
def readFromStream(self, stream):
return self._flavor_reader.processGCodeStream(stream)
def readFromStream(self, stream: str, filename: str) -> Optional["CuraSceneNode"]:
if self._flavor_reader is None:
return None
return self._flavor_reader.processGCodeStream(stream, filename)
def _read(self, file_name):
def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
with open(file_name, "r", encoding = "utf-8") as file:
file_data = file.read()
return self.readFromStream(file_data)
result = [] # type: List[SceneNode]
node = self.readFromStream(file_data, file_name)
if node is not None:
result.append(node)
return result

View file

@ -3,6 +3,6 @@
"author": "Victor Larchenko, Ultimaker",
"version": "1.0.1",
"description": "Allows loading and displaying G-code files.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import re # For escaping characters in the settings.
@ -9,8 +9,7 @@ from UM.Mesh.MeshWriter import MeshWriter
from UM.Logger import Logger
from UM.Application import Application
from UM.Settings.InstanceContainer import InstanceContainer
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.Machines.ContainerTree import ContainerTree
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -117,17 +116,24 @@ class GCodeWriter(MeshWriter):
# \return A serialised string of the settings.
def _serialiseSettings(self, stack):
container_registry = self._application.getContainerRegistry()
quality_manager = self._application.getQualityManager()
prefix = self._setting_keyword + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix)
quality_type = stack.quality.getMetaDataEntry("quality_type")
container_with_profile = stack.qualityChanges
machine_definition_id_for_quality = ContainerTree.getInstance().machines[stack.definition.getId()].quality_definition
if container_with_profile.getId() == "empty_quality_changes":
# If the global quality changes is empty, create a new one
quality_name = container_registry.uniqueName(stack.quality.getName())
container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
quality_id = container_registry.uniqueName((stack.definition.getId() + "_" + quality_name).lower().replace(" ", "_"))
container_with_profile = InstanceContainer(quality_id)
container_with_profile.setName(quality_name)
container_with_profile.setMetaDataEntry("type", "quality_changes")
container_with_profile.setMetaDataEntry("quality_type", quality_type)
if stack.getMetaDataEntry("position") is not None: # For extruder stacks, the quality changes should include an intent category.
container_with_profile.setMetaDataEntry("intent_category", stack.intent.getMetaDataEntry("intent_category", "default"))
container_with_profile.setDefinition(machine_definition_id_for_quality)
flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
# If the quality changes is not set, we need to set type manually
@ -139,7 +145,6 @@ class GCodeWriter(MeshWriter):
flat_global_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
# Get the machine definition ID for quality profiles
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
serialized = flat_global_container.serialize()
@ -151,7 +156,12 @@ class GCodeWriter(MeshWriter):
if extruder_quality.getId() == "empty_quality_changes":
# Same story, if quality changes is empty, create a new one
quality_name = container_registry.uniqueName(stack.quality.getName())
extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
quality_id = container_registry.uniqueName((stack.definition.getId() + "_" + quality_name).lower().replace(" ", "_"))
extruder_quality = InstanceContainer(quality_id)
extruder_quality.setName(quality_name)
extruder_quality.setMetaDataEntry("type", "quality_changes")
extruder_quality.setMetaDataEntry("quality_type", quality_type)
extruder_quality.setDefinition(machine_definition_id_for_quality)
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
# If the quality changes is not set, we need to set type manually

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Writes g-code to a file.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -123,7 +123,7 @@ UM.Dialog
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","By default, white pixels represent high points on the mesh and black pixels represent low points on the mesh. Change this option to reverse the behavior such that black pixels represent high points on the mesh and white pixels represent low points on the mesh.")
text: catalog.i18nc("@info:tooltip","For lithophanes dark pixels should correspond to thicker locations in order to block more light coming through. For height maps lighter pixels signify higher terrain, so lighter pixels should correspond to thicker locations in the generated 3D model.")
Row {
width: parent.width
@ -134,15 +134,61 @@ UM.Dialog
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: image_color_invert
objectName: "Image_Color_Invert"
model: [ catalog.i18nc("@item:inlistbox","Lighter is higher"), catalog.i18nc("@item:inlistbox","Darker is higher") ]
id: lighter_is_higher
objectName: "Lighter_Is_Higher"
model: [ catalog.i18nc("@item:inlistbox","Darker is higher"), catalog.i18nc("@item:inlistbox","Lighter is higher") ]
width: 180 * screenScaleFactor
onCurrentIndexChanged: { manager.onImageColorInvertChanged(currentIndex) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","For lithophanes a simple logarithmic model for translucency is available. For height maps the pixel values correspond to heights linearly.")
Row {
width: parent.width
Label {
text: "Color Model"
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: color_model
objectName: "ColorModel"
model: [ catalog.i18nc("@item:inlistbox","Linear"), catalog.i18nc("@item:inlistbox","Translucency") ]
width: 180 * screenScaleFactor
onCurrentIndexChanged: { manager.onColorModelChanged(currentIndex) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","The percentage of light penetrating a print with a thickness of 1 millimeter. Lowering this value increases the contrast in dark regions and decreases the contrast in light regions of the image.")
visible: color_model.currentText == catalog.i18nc("@item:inlistbox","Translucency")
Row {
width: parent.width
Label {
text: catalog.i18nc("@action:label", "1mm Transmittance (%)")
width: 150 * screenScaleFactor
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: transmittance
objectName: "Transmittance"
focus: true
validator: RegExpValidator {regExp: /^[1-9]\d{0,2}([\,|\.]\d*)?$/}
width: 180 * screenScaleFactor
onTextChanged: { manager.onTransmittanceChanged(text) }
}
}
}
UM.TooltipArea {
Layout.fillWidth:true
height: childrenRect.height

View file

@ -3,6 +3,8 @@
import numpy
import math
from PyQt5.QtGui import QImage, qRed, qGreen, qBlue
from PyQt5.QtCore import Qt
@ -46,9 +48,9 @@ class ImageReader(MeshReader):
def _read(self, file_name):
size = max(self._ui.getWidth(), self._ui.getDepth())
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert)
return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.lighter_is_higher, self._ui.use_transparency_model, self._ui.transmittance_1mm)
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, image_color_invert):
def _generateSceneNode(self, file_name, xz_size, peak_height, base_height, blur_iterations, max_size, lighter_is_higher, use_transparency_model, transmittance_1mm):
scene_node = SceneNode()
mesh = MeshBuilder()
@ -99,12 +101,14 @@ class ImageReader(MeshReader):
for x in range(0, width):
for y in range(0, height):
qrgb = img.pixel(x, y)
avg = float(qRed(qrgb) + qGreen(qrgb) + qBlue(qrgb)) / (3 * 255)
height_data[y, x] = avg
if use_transparency_model:
height_data[y, x] = (0.299 * math.pow(qRed(qrgb) / 255.0, 2.2) + 0.587 * math.pow(qGreen(qrgb) / 255.0, 2.2) + 0.114 * math.pow(qBlue(qrgb) / 255.0, 2.2))
else:
height_data[y, x] = (0.212655 * qRed(qrgb) + 0.715158 * qGreen(qrgb) + 0.072187 * qBlue(qrgb)) / 255 # fast computation ignoring gamma and degamma
Job.yieldThread()
if image_color_invert:
if lighter_is_higher == use_transparency_model:
height_data = 1 - height_data
for _ in range(0, blur_iterations):
@ -124,8 +128,15 @@ class ImageReader(MeshReader):
Job.yieldThread()
height_data *= scale_vector.y
height_data += base_height
if use_transparency_model:
divisor = 1.0 / math.log(transmittance_1mm / 100.0) # log-base doesn't matter here. Precompute this value for faster computation of each pixel.
min_luminance = (transmittance_1mm / 100.0) ** (peak_height - base_height)
for (y, x) in numpy.ndindex(height_data.shape):
mapped_luminance = min_luminance + (1.0 - min_luminance) * height_data[y, x]
height_data[y, x] = base_height + divisor * math.log(mapped_luminance) # use same base as a couple lines above this
else:
height_data *= scale_vector.y
height_data += base_height
heightmap_face_count = 2 * height_minus_one * width_minus_one
total_face_count = heightmap_face_count + (width_minus_one * 2) * (height_minus_one * 2) + 2

View file

@ -30,10 +30,12 @@ class ImageReaderUI(QObject):
self._width = self.default_width
self._depth = self.default_depth
self.base_height = 1
self.peak_height = 10
self.base_height = 0.4
self.peak_height = 2.5
self.smoothing = 1
self.image_color_invert = False;
self.lighter_is_higher = False;
self.use_transparency_model = True;
self.transmittance_1mm = 50.0; # based on pearl PLA
self._ui_lock = threading.Lock()
self._cancelled = False
@ -75,6 +77,7 @@ class ImageReaderUI(QObject):
self._ui_view.findChild(QObject, "Base_Height").setProperty("text", str(self.base_height))
self._ui_view.findChild(QObject, "Peak_Height").setProperty("text", str(self.peak_height))
self._ui_view.findChild(QObject, "Transmittance").setProperty("text", str(self.transmittance_1mm))
self._ui_view.findChild(QObject, "Smoothing").setProperty("value", self.smoothing)
def _createConfigUI(self):
@ -143,4 +146,12 @@ class ImageReaderUI(QObject):
@pyqtSlot(int)
def onImageColorInvertChanged(self, value):
self.image_color_invert = (value == 1)
self.lighter_is_higher = (value == 1)
@pyqtSlot(int)
def onColorModelChanged(self, value):
self.use_transparency_model = (value == 0)
@pyqtSlot(int)
def onTransmittanceChanged(self, value):
self.transmittance_1mm = value

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Enables ability to generate printable geometry from 2D image files.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -157,7 +157,7 @@ class LegacyProfileReader(ProfileReader):
data = stream.getvalue()
profile = InstanceContainer(profile_id)
profile.deserialize(data) # Also performs the version upgrade.
profile.deserialize(data, file_name) # Also performs the version upgrade.
profile.setDirty(True)
#We need to return one extruder stack and one global stack.

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides support for importing profiles from legacy Cura versions.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -5,6 +5,8 @@ import configparser # An input for some functions we're testing.
import os.path # To find the integration test .ini files.
import pytest # To register tests with.
import unittest.mock # To mock the application, plug-in and container registry out.
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import UM.Application # To mock the application out.
import UM.PluginRegistry # To mock the plug-in registry out.
@ -12,11 +14,14 @@ import UM.Settings.ContainerRegistry # To mock the container registry out.
import UM.Settings.InstanceContainer # To intercept the serialised data from the read() function.
import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module.
from LegacyProfileReader import LegacyProfileReader # The module we're testing.
@pytest.fixture
def legacy_profile_reader():
return LegacyProfileReader()
try:
return LegacyProfileReaderModule.LegacyProfileReader()
except TypeError:
return LegacyProfileReaderModule.LegacyProfileReader.LegacyProfileReader()
test_prepareDefaultsData = [
{
@ -157,7 +162,7 @@ def test_read(legacy_profile_reader, file_name):
plugin_registry.getPluginPath = unittest.mock.MagicMock(return_value = os.path.dirname(LegacyProfileReaderModule.__file__))
# Mock out the resulting InstanceContainer so that we can intercept the data before it's passed through the version upgrader.
def deserialize(self, data): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing.
def deserialize(self, data, filename): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing.
global intercepted_data
intercepted_data = data
@ -187,4 +192,4 @@ def test_read(legacy_profile_reader, file_name):
assert parser["metadata"]["type"] == "quality_changes"
assert parser["metadata"]["quality_type"] == "normal"
assert parser["metadata"]["position"] == "0"
assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded.
assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded.

View file

@ -1,16 +1,24 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtProperty
import UM.i18n
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Util import parseBool
import cura.CuraApplication # Imported like this to prevent circular dependencies.
from cura.MachineAction import MachineAction
from cura.Machines.ContainerTree import ContainerTree # To re-build the machine node when hasMaterials changes.
from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.cura_empty_instance_containers import isEmptyContainer
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
catalog = UM.i18n.i18nCatalog("cura")
@ -18,139 +26,118 @@ catalog = UM.i18n.i18nCatalog("cura")
## This action allows for certain settings that are "machine only") to be modified.
# It automatically detects machine definitions that it knows how to change and attaches itself to those.
class MachineSettingsAction(MachineAction):
def __init__(self, parent = None):
def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
self._qml_url = "MachineSettingsAction.qml"
self._application = Application.getInstance()
self._global_container_stack = None
from cura.CuraApplication import CuraApplication
self._application = CuraApplication.getInstance()
from cura.Settings.CuraContainerStack import _ContainerIndexes
self._container_index = _ContainerIndexes.DefinitionChanges
self._store_container_index = _ContainerIndexes.DefinitionChanges
self._container_registry = ContainerRegistry.getInstance()
self._container_registry.containerAdded.connect(self._onContainerAdded)
self._container_registry.containerRemoved.connect(self._onContainerRemoved)
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
# The machine settings dialog blocks auto-slicing when it's shown, and re-enables it when it's finished.
self._backend = self._application.getBackend()
self.onFinished.connect(self._onFinished)
self._empty_definition_container_id_list = []
# If the g-code flavour changes between UltiGCode and another flavour, we need to update the container tree.
self._application.globalContainerStackChanged.connect(self._updateHasMaterialsInContainerTree)
def _isEmptyDefinitionChanges(self, container_id: str):
if not self._empty_definition_container_id_list:
self._empty_definition_container_id_list = [self._application.empty_container.getId(),
self._application.empty_definition_changes_container.getId()]
return container_id in self._empty_definition_container_id_list
# Which container index in a stack to store machine setting changes.
@pyqtProperty(int, constant = True)
def storeContainerIndex(self) -> int:
return self._store_container_index
def _onContainerAdded(self, container):
# Add this action as a supported action to all machine definitions
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
def _onContainerRemoved(self, container):
# Remove definition_changes containers when a stack is removed
if container.getMetaDataEntry("type") in ["machine", "extruder_train"]:
definition_changes_id = container.definitionChanges.getId()
if self._isEmptyDefinitionChanges(definition_changes_id):
return
## Triggered when the global container stack changes or when the g-code
# flavour setting is changed.
def _updateHasMaterialsInContainerTree(self) -> None:
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
return
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
if machine_node.has_materials != parseBool(global_stack.getMetaDataEntry("has_materials")): # May have changed due to the g-code flavour.
machine_node.has_materials = parseBool(global_stack.getMetaDataEntry("has_materials"))
machine_node._loadAll()
def _reset(self):
if not self._global_container_stack:
global_stack = self._application.getMachineManager().activeMachine
if not global_stack:
return
# Make sure there is a definition_changes container to store the machine settings
definition_changes_id = self._global_container_stack.definitionChanges.getId()
if self._isEmptyDefinitionChanges(definition_changes_id):
CuraStackBuilder.createDefinitionChangesContainer(self._global_container_stack,
self._global_container_stack.getName() + "_settings")
# Notify the UI in which container to store the machine settings data
from cura.Settings.CuraContainerStack import _ContainerIndexes
container_index = _ContainerIndexes.DefinitionChanges
if container_index != self._container_index:
self._container_index = container_index
self.containerIndexChanged.emit()
definition_changes_id = global_stack.definitionChanges.getId()
if isEmptyContainer(definition_changes_id):
CuraStackBuilder.createDefinitionChangesContainer(global_stack,
global_stack.getName() + "_settings")
# Disable auto-slicing while the MachineAction is showing
if self._backend: # This sometimes triggers before backend is loaded.
self._backend.disableTimer()
@pyqtSlot()
def onFinishAction(self):
# Restore autoslicing when the machineaction is dismissed
def _onFinished(self):
# Restore auto-slicing when the machine action is dismissed
if self._backend and self._backend.determineAutoSlicing():
self._backend.enableTimer()
self._backend.tickle()
containerIndexChanged = pyqtSignal()
@pyqtProperty(int, notify = containerIndexChanged)
def containerIndex(self):
return self._container_index
def _onGlobalContainerChanged(self):
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
# This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal
self.globalContainerChanged.emit()
globalContainerChanged = pyqtSignal()
@pyqtProperty(int, notify = globalContainerChanged)
def definedExtruderCount(self):
if not self._global_container_stack:
return 0
return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
@pyqtSlot(int)
def setMachineExtruderCount(self, extruder_count):
def setMachineExtruderCount(self, extruder_count: int) -> None:
# Note: this method was in this class before, but since it's quite generic and other plugins also need it
# it was moved to the machine manager instead. Now this method just calls the machine manager.
self._application.getMachineManager().setActiveMachineExtruderCount(extruder_count)
@pyqtSlot()
def forceUpdate(self):
def forceUpdate(self) -> None:
# Force rebuilding the build volume by reloading the global container stack.
# This is a bit of a hack, but it seems quick enough.
self._application.globalContainerStackChanged.emit()
self._application.getMachineManager().globalContainerChanged.emit()
@pyqtSlot()
def updateHasMaterialsMetadata(self):
def updateHasMaterialsMetadata(self) -> None:
global_stack = self._application.getMachineManager().activeMachine
# Updates the has_materials metadata flag after switching gcode flavor
if not self._global_container_stack:
if not global_stack:
return
definition = self._global_container_stack.getBottom()
if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False):
definition = global_stack.getDefinition()
if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or parseBool(definition.getMetaDataEntry("has_materials", False)):
# In other words: only continue for the UM2 (extended), but not for the UM2+
return
machine_manager = self._application.getMachineManager()
material_manager = self._application.getMaterialManager()
extruder_positions = list(self._global_container_stack.extruders.keys())
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
material_node = None
if has_materials:
self._global_container_stack.setMetaDataEntry("has_materials", True)
global_stack.setMetaDataEntry("has_materials", True)
else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.removeMetaDataEntry("has_materials")
if "has_materials" in global_stack.getMetaData():
global_stack.removeMetaDataEntry("has_materials")
self._updateHasMaterialsInContainerTree()
# set materials
for position in extruder_positions:
if has_materials:
material_node = material_manager.getDefaultMaterial(self._global_container_stack, position, None)
machine_manager.setMaterial(position, material_node)
machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()]
for position, extruder in enumerate(global_stack.extruderList):
#Find out what material we need to default to.
approximate_diameter = round(extruder.getProperty("material_diameter", "value"))
material_node = machine_node.variants[extruder.variant.getName()].preferredMaterial(approximate_diameter)
machine_manager.setMaterial(str(position), material_node)
self._application.globalContainerStackChanged.emit()
@pyqtSlot(int)
def updateMaterialForDiameter(self, extruder_position: int):
def updateMaterialForDiameter(self, extruder_position: int) -> None:
# Updates the material container to a material that matches the material diameter set for the printer
self._application.getMachineManager().updateMaterialWithVariant(str(extruder_position))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,182 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.3 as UM
import Cura 1.1 as Cura
//
// This component contains the content for the "Welcome" page of the welcome on-boarding process.
//
Item
{
id: base
UM.I18nCatalog { id: catalog; name: "cura" }
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
property int labelWidth: 210 * screenScaleFactor
property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0
property var labelFont: UM.Theme.getFont("default")
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
property int columnSpacing: 3 * screenScaleFactor
property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
property string extruderStackId: ""
property int extruderPosition: 0
property var forceUpdateFunction: manager.forceUpdate
function updateMaterialDiameter()
{
manager.updateMaterialForDiameter(extruderPosition)
}
Item
{
id: upperBlock
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: UM.Theme.getSize("default_margin").width
height: childrenRect.height
// =======================================
// Left-side column "Nozzle Settings"
// =======================================
Column
{
anchors.top: parent.top
anchors.left: parent.left
width: parent.width * 2 / 3
spacing: base.columnSpacing
Label // Title Label
{
text: catalog.i18nc("@title:label", "Nozzle Settings")
font: UM.Theme.getFont("medium_bold")
renderType: Text.NativeRendering
}
Cura.NumericTextFieldWithUnit // "Nozzle size"
{
id: extruderNozzleSizeField
visible: !Cura.MachineManager.activeMachine.hasVariants
containerStackId: base.extruderStackId
settingKey: "machine_nozzle_size"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Nozzle size")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.NumericTextFieldWithUnit // "Compatible material diameter"
{
id: extruderCompatibleMaterialDiameterField
containerStackId: base.extruderStackId
settingKey: "material_diameter"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Compatible material diameter")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
forceUpdateOnChangeFunction: forceUpdateFunction
// Other modules won't automatically respond after the user changes the value, so we need to force it.
afterOnEditingFinishedFunction: updateMaterialDiameter
}
Cura.NumericTextFieldWithUnit // "Nozzle offset X"
{
id: extruderNozzleOffsetXField
containerStackId: base.extruderStackId
settingKey: "machine_nozzle_offset_x"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Nozzle offset X")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
allowNegativeValue: true
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.NumericTextFieldWithUnit // "Nozzle offset Y"
{
id: extruderNozzleOffsetYField
containerStackId: base.extruderStackId
settingKey: "machine_nozzle_offset_y"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Nozzle offset Y")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
allowNegativeValue: true
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.NumericTextFieldWithUnit // "Cooling Fan Number"
{
id: extruderNozzleCoolingFanNumberField
containerStackId: base.extruderStackId
settingKey: "machine_extruder_cooling_fan_number"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Cooling Fan Number")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: ""
forceUpdateOnChangeFunction: forceUpdateFunction
}
}
}
Item // Extruder Start and End G-code
{
id: lowerBlock
anchors.top: upperBlock.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: UM.Theme.getSize("default_margin").width
Cura.GcodeTextArea // "Extruder Start G-code"
{
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
width: base.columnWidth - UM.Theme.getSize("default_margin").width
labelText: catalog.i18nc("@title:label", "Extruder Start G-code")
containerStackId: base.extruderStackId
settingKey: "machine_extruder_start_code"
settingStoreIndex: propertyStoreIndex
}
Cura.GcodeTextArea // "Extruder End G-code"
{
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
anchors.right: parent.right
width: base.columnWidth - UM.Theme.getSize("default_margin").width
labelText: catalog.i18nc("@title:label", "Extruder End G-code")
containerStackId: base.extruderStackId
settingKey: "machine_extruder_end_code"
settingStoreIndex: propertyStoreIndex
}
}
}

View file

@ -0,0 +1,384 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.3 as UM
import Cura 1.1 as Cura
//
// This the content in the "Printer" tab in the Machine Settings dialog.
//
Item
{
id: base
UM.I18nCatalog { id: catalog; name: "cura" }
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
property int columnSpacing: 3 * screenScaleFactor
property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
property int labelWidth: (columnWidth * 2 / 3 - UM.Theme.getSize("default_margin").width * 2) | 0
property int controlWidth: (columnWidth / 3) | 0
property var labelFont: UM.Theme.getFont("default")
property string machineStackId: Cura.MachineManager.activeMachine.id
property var forceUpdateFunction: manager.forceUpdate
RowLayout
{
id: upperBlock
anchors
{
top: parent.top
left: parent.left
right: parent.right
margins: UM.Theme.getSize("default_margin").width
}
spacing: UM.Theme.getSize("default_margin").width
// =======================================
// Left-side column for "Printer Settings"
// =======================================
Column
{
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: base.columnSpacing
Label // Title Label
{
text: catalog.i18nc("@title:label", "Printer Settings")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
width: parent.width
elide: Text.ElideRight
}
Cura.NumericTextFieldWithUnit // "X (Width)"
{
id: machineXWidthField
containerStackId: machineStackId
settingKey: "machine_width"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "X (Width)")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.NumericTextFieldWithUnit // "Y (Depth)"
{
id: machineYDepthField
containerStackId: machineStackId
settingKey: "machine_depth"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Y (Depth)")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.NumericTextFieldWithUnit // "Z (Height)"
{
id: machineZHeightField
containerStackId: machineStackId
settingKey: "machine_height"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Z (Height)")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.ComboBoxWithOptions // "Build plate shape"
{
id: buildPlateShapeComboBox
containerStackId: machineStackId
settingKey: "machine_shape"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Build plate shape")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.SimpleCheckBox // "Origin at center"
{
id: originAtCenterCheckBox
containerStackId: machineStackId
settingKey: "machine_center_is_zero"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Origin at center")
labelFont: base.labelFont
labelWidth: base.labelWidth
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.SimpleCheckBox // "Heated bed"
{
id: heatedBedCheckBox
containerStackId: machineStackId
settingKey: "machine_heated_bed"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Heated bed")
labelFont: base.labelFont
labelWidth: base.labelWidth
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.SimpleCheckBox // "Heated build volume"
{
id: heatedVolumeCheckBox
containerStackId: machineStackId
settingKey: "machine_heated_build_volume"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Heated build volume")
labelFont: base.labelFont
labelWidth: base.labelWidth
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.ComboBoxWithOptions // "G-code flavor"
{
id: gcodeFlavorComboBox
containerStackId: machineStackId
settingKey: "machine_gcode_flavor"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "G-code flavor")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
forceUpdateOnChangeFunction: forceUpdateFunction
// FIXME(Lipu): better document this.
// This has something to do with UM2 and UM2+ regarding "has_material" and the gcode flavor settings.
// I don't remember exactly what.
afterOnEditingFinishedFunction: manager.updateHasMaterialsMetadata
}
}
// =======================================
// Right-side column for "Printhead Settings"
// =======================================
Column
{
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: base.columnSpacing
Label // Title Label
{
text: catalog.i18nc("@title:label", "Printhead Settings")
font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
width: parent.width
elide: Text.ElideRight
}
Cura.PrintHeadMinMaxTextField // "X min"
{
id: machineXMinField
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "X min")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
axisName: "x"
axisMinOrMax: "min"
allowNegativeValue: true
allowPositiveValue: false
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.PrintHeadMinMaxTextField // "Y min"
{
id: machineYMinField
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Y min")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
axisName: "y"
axisMinOrMax: "min"
allowNegativeValue: true
allowPositiveValue: false
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.PrintHeadMinMaxTextField // "X max"
{
id: machineXMaxField
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "X max")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
axisName: "x"
axisMinOrMax: "max"
allowNegativeValue: false
allowPositiveValue: true
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.PrintHeadMinMaxTextField // "Y max"
{
id: machineYMaxField
containerStackId: machineStackId
settingKey: "machine_head_with_fans_polygon"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Y max")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
axisName: "y"
axisMinOrMax: "max"
allowNegativeValue: false
allowPositiveValue: true
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.NumericTextFieldWithUnit // "Gantry Height"
{
id: machineGantryHeightField
containerStackId: machineStackId
settingKey: "gantry_height"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Gantry Height")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
unitText: catalog.i18nc("@label", "mm")
forceUpdateOnChangeFunction: forceUpdateFunction
}
Cura.ComboBoxWithOptions // "Number of Extruders"
{
id: numberOfExtrudersComboBox
containerStackId: machineStackId
settingKey: "machine_extruder_count"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Number of Extruders")
labelFont: base.labelFont
labelWidth: base.labelWidth
controlWidth: base.controlWidth
forceUpdateOnChangeFunction: forceUpdateFunction
// FIXME(Lipu): better document this.
// This has something to do with UM2 and UM2+ regarding "has_material" and the gcode flavor settings.
// I don't remember exactly what.
afterOnEditingFinishedFunction: manager.updateHasMaterialsMetadata
setValueFunction: manager.setMachineExtruderCount
optionModel: ListModel
{
id: extruderCountModel
Component.onCompleted:
{
update()
}
function update()
{
clear()
for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++)
{
// Use String as value. JavaScript only has Number. PropertyProvider.setPropertyValue()
// takes a QVariant as value, and Number gets translated into a float. This will cause problem
// for integer settings such as "Number of Extruders".
append({ text: String(i), value: String(i) })
}
}
}
Connections
{
target: Cura.MachineManager
onGlobalContainerChanged: extruderCountModel.update()
}
}
Cura.SimpleCheckBox // "Shared Heater"
{
id: sharedHeaterCheckBox
containerStackId: machineStackId
settingKey: "machine_extruders_share_heater"
settingStoreIndex: propertyStoreIndex
labelText: catalog.i18nc("@label", "Shared Heater")
labelFont: base.labelFont
labelWidth: base.labelWidth
forceUpdateOnChangeFunction: forceUpdateFunction
}
}
}
RowLayout // Start and End G-code
{
id: lowerBlock
spacing: UM.Theme.getSize("default_margin").width
anchors
{
top: upperBlock.bottom
bottom: parent.bottom
left: parent.left
right: parent.right
margins: UM.Theme.getSize("default_margin").width
}
Cura.GcodeTextArea // "Start G-code"
{
Layout.fillWidth: true
Layout.fillHeight: true
labelText: catalog.i18nc("@title:label", "Start G-code")
containerStackId: machineStackId
settingKey: "machine_start_gcode"
settingStoreIndex: propertyStoreIndex
}
Cura.GcodeTextArea // "End G-code"
{
Layout.fillWidth: true
Layout.fillHeight: true
labelText: catalog.i18nc("@title:label", "End G-code")
containerStackId: machineStackId
settingKey: "machine_end_gcode"
settingStoreIndex: propertyStoreIndex
}
}
}

View file

@ -3,6 +3,6 @@
"author": "fieldOfView",
"version": "1.0.1",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -3,12 +3,13 @@
import os
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty, QTimer
from UM.Application import Application
from UM.Extension import Extension
from UM.Logger import Logger
from UM.Message import Message
from UM.Scene.Camera import Camera
from UM.i18n import i18nCatalog
from UM.PluginRegistry import PluginRegistry
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -29,13 +30,22 @@ class ModelChecker(QObject, Extension):
lifetime = 0,
title = catalog.i18nc("@info:title", "3D Model Assistant"))
self._change_timer = QTimer()
self._change_timer.setInterval(200)
self._change_timer.setSingleShot(True)
self._change_timer.timeout.connect(self.onChanged)
Application.getInstance().initializationFinished.connect(self._pluginsInitialized)
Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged)
Application.getInstance().globalContainerStackChanged.connect(self._onChanged)
## Pass-through to allow UM.Signal to connect with a pyqtSignal.
def _onChanged(self, *args, **kwargs):
self.onChanged.emit()
# Ignore camera updates.
if len(args) == 0:
self._change_timer.start()
return
if not isinstance(args[0], Camera):
self._change_timer.start()
## Called when plug-ins are initialized.
#
@ -66,7 +76,9 @@ class ModelChecker(QObject, Extension):
# This function can be triggered in the middle of a machine change, so do not proceed if the machine change
# has not done yet.
if str(node_extruder_position) not in global_container_stack.extruders:
try:
extruder = global_container_stack.extruderList[int(node_extruder_position)]
except IndexError:
Application.getInstance().callLater(lambda: self.onChanged.emit())
return False
@ -121,9 +133,9 @@ class ModelChecker(QObject, Extension):
material_shrinkage = {}
# Get all shrinkage values of materials used
for extruder_position, extruder in global_container_stack.extruders.items():
for extruder_position, extruder in enumerate(global_container_stack.extruderList):
shrinkage = extruder.material.getProperty("material_shrinkage_percentage", "value")
if shrinkage is None:
shrinkage = 0
material_shrinkage[extruder_position] = shrinkage
material_shrinkage[str(extruder_position)] = shrinkage
return material_shrinkage

View file

@ -2,7 +2,7 @@
"name": "Model Checker",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"api": "6.0",
"api": "7.0",
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"i18n-catalog": "cura"
}

View file

@ -11,13 +11,21 @@ Rectangle
{
id: viewportOverlay
property bool isConnected: Cura.MachineManager.activeMachineHasActiveNetworkConnection || Cura.MachineManager.activeMachineHasActiveCloudConnection
property bool isNetworkConfigurable: ["Ultimaker 3", "Ultimaker 3 Extended", "Ultimaker S5"].indexOf(Cura.MachineManager.activeMachineDefinitionName) > -1
property bool isConnected: Cura.MachineManager.activeMachineHasNetworkConnection || Cura.MachineManager.activeMachineHasCloudConnection
property bool isNetworkConfigurable:
{
if(Cura.MachineManager.activeMachine === null)
{
return false
}
return Cura.MachineManager.activeMachine.supportsNetworkConnection
}
property bool isNetworkConfigured:
{
// Readability:
var connectedTypes = [2, 3];
var types = Cura.MachineManager.activeMachineConfiguredConnectionTypes
var types = Cura.MachineManager.activeMachine.configuredConnectionTypes
// Check if configured connection types includes either 2 or 3 (LAN or cloud)
for (var i = 0; i < types.length; i++)
@ -89,7 +97,7 @@ Rectangle
horizontalCenter: parent.horizontalCenter
}
visible: isNetworkConfigured && !isConnected
text: catalog.i18nc("@info", "Please make sure your printer has a connection:\n- Check if the printer is turned on.\n- Check if the printer is connected to the network.")
text: catalog.i18nc("@info", "Please make sure your printer has a connection:\n- Check if the printer is turned on.\n- Check if the printer is connected to the network.\n- Check if you are signed in to discover cloud-connected printers.")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("monitor_text_primary")
wrapMode: Text.WordWrap
@ -98,7 +106,6 @@ Rectangle
width: contentWidth
}
// CASE 3: CAN NOT MONITOR
Label
{
id: noNetworkLabel
@ -106,24 +113,8 @@ Rectangle
{
horizontalCenter: parent.horizontalCenter
}
visible: !isNetworkConfigured
text: catalog.i18nc("@info", "Please select a network connected printer to monitor.")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("monitor_text_primary")
wrapMode: Text.WordWrap
width: contentWidth
lineHeight: UM.Theme.getSize("monitor_text_line_large").height
lineHeightMode: Text.FixedHeight
}
Label
{
id: noNetworkUltimakerLabel
anchors
{
horizontalCenter: parent.horizontalCenter
}
visible: !isNetworkConfigured && isNetworkConfigurable
text: catalog.i18nc("@info", "Please connect your Ultimaker printer to your local network.")
text: catalog.i18nc("@info", "Please connect your printer to the network.")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("monitor_text_primary")
wrapMode: Text.WordWrap
@ -135,7 +126,7 @@ Rectangle
{
anchors
{
left: noNetworkUltimakerLabel.left
left: noNetworkLabel.left
}
visible: !isNetworkConfigured && isNetworkConfigurable
height: UM.Theme.getSize("monitor_text_line").height
@ -160,7 +151,7 @@ Rectangle
verticalCenter: externalLinkIcon.verticalCenter
}
color: UM.Theme.getColor("monitor_text_link")
font: UM.Theme.getFont("medium") // 14pt, regular
font: UM.Theme.getFont("medium")
linkColor: UM.Theme.getColor("monitor_text_link")
text: catalog.i18nc("@label link to technical assistance", "View user manuals online")
renderType: Text.NativeRendering
@ -170,15 +161,9 @@ Rectangle
anchors.fill: parent
hoverEnabled: true
onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers")
onEntered:
{
manageQueueText.font.underline = true
}
onExited:
{
manageQueueText.font.underline = false
}
onEntered: manageQueueText.font.underline = true
onExited: manageQueueText.font.underline = false
}
}
}
}
}

View file

@ -2,8 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher.
import os.path
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
from cura.Stages.CuraStage import CuraStage

View file

@ -12,7 +12,7 @@ def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Monitor"),
"weight": 2
"weight": 30
}
}

View file

@ -1,8 +1,8 @@
{
"name": "Monitor Stage",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides a monitor stage in Cura.",
"api": "6.0",
"i18n-catalog": "cura"
{
"name": "Monitor Stage",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides a monitor stage in Cura.",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -29,6 +29,17 @@ UM.TooltipArea
UM.ActiveTool.forceUpdate();
}
}
// When the user removes settings from the list addedSettingsModel, we need to recheck if the
// setting is visible or not to show a mark in the CheckBox.
Connections
{
target: addedSettingsModel
onVisibleCountChanged:
{
check.checked = addedSettingsModel.getVisible(model.key)
}
}
}

View file

@ -1,7 +1,7 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
from PyQt5.QtCore import pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
@ -13,6 +13,7 @@ import UM.Settings.Models.SettingVisibilityHandler
from cura.Settings.ExtruderManager import ExtruderManager #To get global-inherits-stack setting values from different extruders.
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
## The per object setting visibility handler ensures that only setting
# definitions that have a matching instance Container are returned as visible.
class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):

View file

@ -4,22 +4,68 @@
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Window 2.2
import UM 1.2 as UM
import Cura 1.0 as Cura
import ".."
Item {
id: base;
UM.I18nCatalog { id: catalog; name: "cura"; }
width: childrenRect.width;
height: childrenRect.height;
property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
Item
{
id: base
width: childrenRect.width
height: childrenRect.height
property var allCategoriesExceptSupport: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
"travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
readonly property string normalMeshType: ""
readonly property string supportMeshType: "support_mesh"
readonly property string cuttingMeshType: "cutting_mesh"
readonly property string infillMeshType: "infill_mesh"
readonly property string antiOverhangMeshType: "anti_overhang_mesh"
property var currentMeshType: UM.ActiveTool.properties.getValue("MeshType")
// Update the view every time the currentMeshType changes
onCurrentMeshTypeChanged:
{
var type = currentMeshType
// set checked state of mesh type buttons
normalButton.checked = type === normalMeshType
supportMeshButton.checked = type === supportMeshType
overhangMeshButton.checked = type === infillMeshType || type === cuttingMeshType
antiOverhangMeshButton.checked = type === antiOverhangMeshType
// update active type label
for (var button in meshTypeButtons.children)
{
if (meshTypeButtons.children[button].checked){
meshTypeLabel.text = catalog.i18nc("@label", "Mesh Type") + ": " + meshTypeButtons.children[button].text
break
}
}
}
function setOverhangsMeshType()
{
if (infillOnlyCheckbox.checked)
{
setMeshType(infillMeshType)
}
else
{
setMeshType(cuttingMeshType)
}
}
function setMeshType(type)
{
UM.ActiveTool.setProperty("MeshType", type)
}
UM.I18nCatalog { id: catalog; name: "uranium"}
Column
{
id: items
@ -28,123 +74,97 @@ Item {
spacing: UM.Theme.getSize("default_margin").height
Row
Row // Mesh type buttons
{
id: meshTypeButtons
spacing: UM.Theme.getSize("default_margin").width
Label
Button
{
text: catalog.i18nc("@label","Mesh Type")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
height: UM.Theme.getSize("setting").height
verticalAlignment: Text.AlignVCenter
id: normalButton
text: catalog.i18nc("@label", "Normal model")
iconSource: UM.Theme.getIcon("pos_normal");
property bool needBorder: true
checkable: true
onClicked: setMeshType(normalMeshType);
style: UM.Theme.styles.tool_button;
z: 4
}
UM.SettingPropertyProvider
Button
{
id: meshTypePropertyProvider
containerStack: Cura.MachineManager.activeMachine
watchedProperties: [ "enabled" ]
id: supportMeshButton
text: catalog.i18nc("@label", "Print as support")
iconSource: UM.Theme.getIcon("pos_print_as_support");
property bool needBorder: true
checkable:true
onClicked: setMeshType(supportMeshType)
style: UM.Theme.styles.tool_button;
z: 3
}
ComboBox
Button
{
id: meshTypeSelection
style: UM.Theme.styles.combobox
onActivated: {
UM.ActiveTool.setProperty("MeshType", model.get(index).type)
}
model: ListModel
{
id: meshTypeModel
Component.onCompleted: meshTypeSelection.populateModel()
}
function populateModel()
{
meshTypeModel.append({
type: "",
text: catalog.i18nc("@label", "Normal model")
});
meshTypePropertyProvider.key = "support_mesh";
if(meshTypePropertyProvider.properties.enabled == "True")
{
meshTypeModel.append({
type: "support_mesh",
text: catalog.i18nc("@label", "Print as support")
});
}
meshTypePropertyProvider.key = "anti_overhang_mesh";
if(meshTypePropertyProvider.properties.enabled == "True")
{
meshTypeModel.append({
type: "anti_overhang_mesh",
text: catalog.i18nc("@label", "Don't support overlap with other models")
});
}
meshTypePropertyProvider.key = "cutting_mesh";
if(meshTypePropertyProvider.properties.enabled == "True")
{
meshTypeModel.append({
type: "cutting_mesh",
text: catalog.i18nc("@label", "Modify settings for overlap with other models")
});
}
meshTypePropertyProvider.key = "infill_mesh";
if(meshTypePropertyProvider.properties.enabled == "True")
{
meshTypeModel.append({
type: "infill_mesh",
text: catalog.i18nc("@label", "Modify settings for infill of other models")
});
}
meshTypeSelection.updateCurrentIndex();
}
function updateCurrentIndex()
{
var mesh_type = UM.ActiveTool.properties.getValue("MeshType");
meshTypeSelection.currentIndex = -1;
for(var index=0; index < meshTypeSelection.model.count; index++)
{
if(meshTypeSelection.model.get(index).type == mesh_type)
{
meshTypeSelection.currentIndex = index;
return;
}
}
meshTypeSelection.currentIndex = 0;
}
id: overhangMeshButton
text: catalog.i18nc("@label", "Modify settings for overlaps")
iconSource: UM.Theme.getIcon("pos_modify_overlaps");
property bool needBorder: true
checkable:true
onClicked: setMeshType(infillMeshType)
style: UM.Theme.styles.tool_button;
z: 2
}
Connections
Button
{
target: Cura.MachineManager
onGlobalContainerChanged:
{
meshTypeSelection.model.clear();
meshTypeSelection.populateModel();
}
}
Connections
{
target: UM.Selection
onSelectionChanged: meshTypeSelection.updateCurrentIndex()
id: antiOverhangMeshButton
text: catalog.i18nc("@label", "Don't support overlaps")
iconSource: UM.Theme.getIcon("pos_modify_dont_support_overlap");
property bool needBorder: true
checkable: true
onClicked: setMeshType(antiOverhangMeshType)
style: UM.Theme.styles.tool_button;
z: 1
}
}
Column
Label
{
id: meshTypeLabel
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
height: UM.Theme.getSize("setting").height
verticalAlignment: Text.AlignVCenter
}
CheckBox
{
id: infillOnlyCheckbox
text: catalog.i18nc("@action:checkbox", "Infill only");
style: UM.Theme.styles.checkbox;
visible: currentMeshType === infillMeshType || currentMeshType === cuttingMeshType
onClicked: setOverhangsMeshType()
Binding
{
target: infillOnlyCheckbox
property: "checked"
value: currentMeshType === infillMeshType
}
}
Column // Settings Dialog
{
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
// It kinda looks ugly otherwise (big panel, no content on it)
id: currentSettings
property int maximumHeight: 200 * screenScaleFactor
height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("default_lining").height), maximumHeight)
visible: meshTypeSelection.model.get(meshTypeSelection.currentIndex).type != "anti_overhang_mesh"
visible: currentMeshType != "anti_overhang_mesh"
ScrollView
{
@ -159,26 +179,26 @@ Item {
model: UM.SettingDefinitionsModel
{
id: addedSettingsModel;
containerId: Cura.MachineManager.activeDefinitionId
id: addedSettingsModel
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
expanded: [ "*" ]
filter:
{
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
{
return {"settable_per_meshgroup": true};
return {"settable_per_meshgroup": true}
}
return {"settable_per_mesh": true};
return {"settable_per_mesh": true}
}
exclude:
{
var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
if (currentMeshType == "support_mesh")
{
excluded_settings = excluded_settings.concat(base.all_categories_except_support);
excluded_settings = excluded_settings.concat(base.allCategoriesExceptSupport)
}
return excluded_settings;
return excluded_settings
}
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
@ -188,8 +208,9 @@ Item {
// For some reason the model object is updated after removing him from the memory and
// it happens only on Windows. For this reason, set the destroyed value manually.
Component.onDestruction: {
setDestroyed(true);
Component.onDestruction:
{
setDestroyed(true)
}
}
@ -213,7 +234,8 @@ Item {
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
asynchronous: model.type != "enum" && model.type != "extruder"
onLoaded: {
onLoaded:
{
settingLoader.item.showRevertButton = false
settingLoader.item.showInheritButton = false
settingLoader.item.showLinkedSettingIcon = false
@ -299,7 +321,7 @@ Item {
target: inheritStackProvider
onPropertiesChanged:
{
provider.forcePropertiesChanged();
provider.forcePropertiesChanged()
}
}
@ -312,22 +334,22 @@ Item {
// so here we connect to the signal and update the those values.
if (typeof UM.ActiveTool.properties.getValue("SelectedObjectId") !== "undefined")
{
const selectedObjectId = UM.ActiveTool.properties.getValue("SelectedObjectId");
const selectedObjectId = UM.ActiveTool.properties.getValue("SelectedObjectId")
if (addedSettingsModel.visibilityHandler.selectedObjectId != selectedObjectId)
{
addedSettingsModel.visibilityHandler.selectedObjectId = selectedObjectId;
addedSettingsModel.visibilityHandler.selectedObjectId = selectedObjectId
}
}
if (typeof UM.ActiveTool.properties.getValue("ContainerID") !== "undefined")
{
const containerId = UM.ActiveTool.properties.getValue("ContainerID");
const containerId = UM.ActiveTool.properties.getValue("ContainerID")
if (provider.containerStackId != containerId)
{
provider.containerStackId = containerId;
provider.containerStackId = containerId
}
if (inheritStackProvider.containerStackId != containerId)
{
inheritStackProvider.containerStackId = containerId;
inheritStackProvider.containerStackId = containerId
}
}
}
@ -337,7 +359,7 @@ Item {
}
}
Button
Cura.SecondaryButton
{
id: customiseSettingsButton;
height: UM.Theme.getSize("setting_control").height;
@ -345,33 +367,12 @@ Item {
text: catalog.i18nc("@action:button", "Select settings");
style: ButtonStyle
{
background: Rectangle
{
width: control.width;
height: control.height;
border.width: UM.Theme.getSize("default_lining").width;
border.color: control.pressed ? UM.Theme.getColor("action_button_active_border") :
control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
color: control.pressed ? UM.Theme.getColor("action_button_active") :
control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
}
label: Label
{
text: control.text;
color: UM.Theme.getColor("setting_control_text");
font: UM.Theme.getFont("default")
anchors.centerIn: parent
}
}
onClicked:
{
settingPickDialog.visible = true;
if (meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
if (currentMeshType == "support_mesh")
{
settingPickDialog.additional_excluded_settings = base.all_categories_except_support;
settingPickDialog.additional_excluded_settings = base.allCategoriesExceptSupport;
}
else
{
@ -379,138 +380,12 @@ Item {
}
}
}
}
UM.Dialog {
SettingPickDialog
{
id: settingPickDialog
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
width: screenScaleFactor * 360
property var additional_excluded_settings
onVisibilityChanged:
{
// force updating the model to sync it with addedSettingsModel
if(visible)
{
// Set skip setting, it will prevent from resetting selected mesh_type
contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
listview.model.forceUpdate()
updateFilter()
}
}
function updateFilter()
{
var new_filter = {};
new_filter["settable_per_mesh"] = true;
// Don't filter on "settable_per_meshgroup" any more when `printSequencePropertyProvider.properties.value`
// is set to "one_at_a_time", because the current backend architecture isn't ready for that.
if(filterInput.text != "")
{
new_filter["i18n_label"] = "*" + filterInput.text;
}
listview.model.filter = new_filter;
}
TextField {
id: filterInput
anchors {
top: parent.top
left: parent.left
right: toggleShowAll.left
rightMargin: UM.Theme.getSize("default_margin").width
}
placeholderText: catalog.i18nc("@label:textbox", "Filter...");
onTextChanged: settingPickDialog.updateFilter()
}
CheckBox
{
id: toggleShowAll
anchors {
top: parent.top
right: parent.right
}
text: catalog.i18nc("@label:checkbox", "Show all")
checked: listview.model.showAll
onClicked:
{
listview.model.showAll = checked;
}
}
ScrollView
{
id: scrollView
anchors
{
top: filterInput.bottom;
left: parent.left;
right: parent.right;
bottom: parent.bottom;
}
ListView
{
id:listview
model: UM.SettingDefinitionsModel
{
id: definitionsModel;
containerId: Cura.MachineManager.activeDefinitionId
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
exclude:
{
var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings);
return excluded_settings;
}
}
delegate:Loader
{
id: loader
width: parent.width
height: model.type != undefined ? UM.Theme.getSize("section").height : 0;
property var definition: model
property var settingDefinitionsModel: definitionsModel
asynchronous: true
source:
{
switch(model.type)
{
case "category":
return "PerObjectCategory.qml"
default:
return "PerObjectItem.qml"
}
}
}
Component.onCompleted: settingPickDialog.updateFilter()
}
}
rightButtons: [
Button {
text: catalog.i18nc("@action:button", "Close");
onClicked: {
settingPickDialog.visible = false;
}
}
]
}
UM.SettingPropertyProvider
@ -533,25 +408,25 @@ Item {
storeIndex: 0
}
SystemPalette { id: palette; }
SystemPalette { id: palette }
Component
{
id: settingTextField;
id: settingTextField
Cura.SettingTextField { }
}
Component
{
id: settingComboBox;
id: settingComboBox
Cura.SettingComboBox { }
}
Component
{
id: settingExtruder;
id: settingExtruder
Cura.SettingExtruder { }
}
@ -565,22 +440,23 @@ Item {
Component
{
id: settingCheckBox;
id: settingCheckBox
Cura.SettingCheckBox { }
}
Component
{
id: settingCategory;
id: settingCategory
Cura.SettingCategory { }
}
Component
{
id: settingUnknown;
id: settingUnknown
Cura.SettingUnknown { }
}
}

View file

@ -1,6 +1,6 @@
# Copyright (c) 2016 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
from UM.Tool import Tool
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -20,15 +20,11 @@ class PerObjectSettingsTool(Tool):
self.setExposedProperties("SelectedObjectId", "ContainerID", "SelectedActiveExtruder", "MeshType")
self._advanced_mode = False
self._multi_extrusion = False
self._single_model_selected = False
Selection.selectionChanged.connect(self.propertyChanged)
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferenceChanged)
self._onPreferenceChanged("cura/active_mode")
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._onGlobalContainerChanged()
Selection.selectionChanged.connect(self._updateEnabled)
@ -70,8 +66,16 @@ class PerObjectSettingsTool(Tool):
selected_object.addDecorator(SettingOverrideDecorator())
selected_object.callDecoration("setActiveExtruder", extruder_stack_id)
def setMeshType(self, mesh_type):
## Returns True when the mesh_type was changed, False when current mesh_type == mesh_type
def setMeshType(self, mesh_type: str) -> bool:
if self.getMeshType() == mesh_type:
return False
selected_object = Selection.getSelectedObject(0)
if selected_object is None:
Logger.log("w", "Tried setting the mesh type of the selected object, but no object was selected")
return False
stack = selected_object.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
if not stack:
selected_object.addDecorator(SettingOverrideDecorator())
@ -90,6 +94,9 @@ class PerObjectSettingsTool(Tool):
new_instance.resetState() # Ensure that the state is not seen as a user state.
settings.addInstance(new_instance)
self.propertyChanged.emit()
return True
def getMeshType(self):
selected_object = Selection.getSelectedObject(0)
stack = selected_object.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
@ -103,11 +110,6 @@ class PerObjectSettingsTool(Tool):
return ""
def _onPreferenceChanged(self, preference):
if preference == "cura/active_mode":
self._advanced_mode = Application.getInstance().getPreferences().getValue(preference) == 1
self._updateEnabled()
def _onGlobalContainerChanged(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
@ -140,4 +142,4 @@ class PerObjectSettingsTool(Tool):
self._single_model_selected = False # Group is selected, so tool needs to be disabled
else:
self._single_model_selected = True
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._single_model_selected)

View file

@ -0,0 +1,139 @@
import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import UM 1.2 as UM
import Cura 1.0 as Cura
import ".."
UM.Dialog
{
id: settingPickDialog
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
width: screenScaleFactor * 360
property var additional_excluded_settings
onVisibilityChanged:
{
// force updating the model to sync it with addedSettingsModel
if (visible)
{
// Set skip setting, it will prevent from resetting selected mesh_type
contents.model.visibilityHandler.addSkipResetSetting(currentMeshType)
listview.model.forceUpdate()
updateFilter()
}
}
function updateFilter()
{
var new_filter = {}
new_filter["settable_per_mesh"] = true
// Don't filter on "settable_per_meshgroup" any more when `printSequencePropertyProvider.properties.value`
// is set to "one_at_a_time", because the current backend architecture isn't ready for that.
if (filterInput.text != "")
{
new_filter["i18n_label"] = "*" + filterInput.text
}
listview.model.filter = new_filter
}
TextField {
id: filterInput
anchors {
top: parent.top
left: parent.left
right: toggleShowAll.left
rightMargin: UM.Theme.getSize("default_margin").width
}
placeholderText: catalog.i18nc("@label:textbox", "Filter...")
onTextChanged: settingPickDialog.updateFilter()
}
CheckBox
{
id: toggleShowAll
anchors {
top: parent.top
right: parent.right
}
text: catalog.i18nc("@label:checkbox", "Show all")
checked: listview.model.showAll
onClicked:
{
listview.model.showAll = checked
}
}
ScrollView
{
id: scrollView
anchors
{
top: filterInput.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
ListView
{
id:listview
model: UM.SettingDefinitionsModel
{
id: definitionsModel
containerId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.definition.id: ""
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
exclude:
{
var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings)
return excluded_settings
}
}
delegate:Loader
{
id: loader
width: parent.width
height: model.type != undefined ? UM.Theme.getSize("section").height : 0
property var definition: model
property var settingDefinitionsModel: definitionsModel
asynchronous: true
source:
{
switch(model.type)
{
case "category":
return "PerObjectCategory.qml"
default:
return "PerObjectItem.qml"
}
}
}
Component.onCompleted: settingPickDialog.updateFilter()
}
}
rightButtons: [
Button {
text: catalog.i18nc("@action:button", "Close")
onClicked: {
settingPickDialog.visible = false
}
}
]
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides the Per Model Settings.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -1,3 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="M30 20L25.1 6.7 27.6 0H12.9l1.6 5H6.4l2.3 6H2.4l2.4 6.2L0 30h19.5l-1.7-4H26l-2.3-6H30zm-12.5 5l-2.8-7.5 2.4-6.5H9.6L7.7 6h14.7L20 12.4l2.9 7.6 2 5h-7.4z"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
<title>per_model_settings</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M9.73076923,0 L9.226,1.345 L0.449,11 L0,11 L0.639,9.084 L8.896,0 L9.73076923,0 Z M8.49,3.472 L8.907,4.721 L3.199,11 L1.647,11 L8.49,3.472 Z M9.228,5.685 L9.645,6.935 L5.949,11 L4.397,11 L9.228,5.685 Z M9.966,7.899 L10.382,9.148 L8.699,11 L7.147,11 L9.966,7.899 Z M10.704,10.112 L11,11 L9.896,11 L10.704,10.112 Z M7.698,0 L1.332,7.004 L2.23,4.308 L6.146,0 L7.698,0 Z M4.948,0 L2.344,2.866 L1.89,1.656 L3.396,0 L4.948,0 Z M2.198,0 L1.54,0.724 L1.26923077,0 L2.198,0 Z" id="path-1"></path>
</defs>
<g id="per_model_settings" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Per-model" transform="translate(2.000000, 2.000000)">
<polygon id="Path-Copy-5" fill="#000" points="1.26923077 0 9.73076923 0 8.46153846 3.38461538 11 11 0 11 2.53846154 3.38461538"></polygon>
<polygon id="Path-Copy-8" fill="#000" points="14.2692308 13 22.7307692 13 21.4615385 16.3846154 24 24 13 24 15.5384615 16.3846154"></polygon>
<g id="stripe" transform="translate(13.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Combined-Shape" fill="#000" xlink:href="#path-1"></use>
</g>
<path d="M1.990731,13.5 L3.06878027,16.374798 L0.693712943,23.5 L10.3062871,23.5 L7.93121973,16.374798 L9.009269,13.5 L1.990731,13.5 Z" id="Path-Copy-7" stroke="#000"></path>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 240 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

@ -162,7 +162,7 @@ class PostProcessingPlugin(QObject, Extension):
loaded_script = importlib.util.module_from_spec(spec)
if spec.loader is None:
continue
spec.loader.exec_module(loaded_script)
spec.loader.exec_module(loaded_script) # type: ignore
sys.modules[script_name] = loaded_script #TODO: This could be a security risk. Overwrite any module with a user-provided name?
loaded_class = getattr(loaded_script, script_name)
@ -219,6 +219,7 @@ class PostProcessingPlugin(QObject, Extension):
self._script_list.clear()
if not new_stack.getMetaDataEntry("post_processing_scripts"): # Missing or empty.
self.scriptListChanged.emit() # Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
self.setSelectedScriptIndex(-1)
return
self._script_list.clear()

View file

@ -2,7 +2,7 @@
"name": "Post Processing",
"author": "Ultimaker",
"version": "2.2.1",
"api": "6.0",
"api": "7.0",
"description": "Extension that allows for user created scripts for post processing",
"catalog": "cura"
}

View file

@ -100,8 +100,8 @@ class ChangeAtZ(Script):
},
"d_twLayers":
{
"label": "No. Layers",
"description": "No. of layers used to change",
"label": "Layer Spread",
"description": "The change will be gradual over this many layers. Enter 1 to make the change immediate.",
"unit": "",
"type": "int",
"default_value": 1,
@ -330,7 +330,7 @@ class ChangeAtZ(Script):
"extruderOne": self.getSettingValueByKey("i2_extruderOne"),
"extruderTwo": self.getSettingValueByKey("i4_extruderTwo"),
"fanSpeed": self.getSettingValueByKey("j2_fanSpeed")}
old = {"speed": -1, "flowrate": -1, "flowrateOne": -1, "flowrateTwo": -1, "platformTemp": -1, "extruderOne": -1,
old = {"speed": -1, "flowrate": 100, "flowrateOne": -1, "flowrateTwo": -1, "platformTemp": -1, "extruderOne": -1,
"extruderTwo": -1, "bedTemp": -1, "fanSpeed": -1, "state": -1}
twLayers = self.getSettingValueByKey("d_twLayers")
if self.getSettingValueByKey("c_behavior") == "single_layer":
@ -367,6 +367,8 @@ class ChangeAtZ(Script):
modified_gcode = ""
lines = active_layer.split("\n")
for line in lines:
if line.strip() == "":
continue
if ";Generated with Cura_SteamEngine" in line:
TWinstances += 1
modified_gcode += ";ChangeAtZ instances: %d\n" % TWinstances
@ -410,6 +412,8 @@ class ChangeAtZ(Script):
tmp_extruder = self.getValue(line, "T", None)
if tmp_extruder == None: #check if extruder is specified
old["flowrate"] = self.getValue(line, "S", old["flowrate"])
if old["flowrate"] == -1:
old["flowrate"] = 100.0
elif tmp_extruder == 0: #first extruder
old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
elif tmp_extruder == 1: #second extruder
@ -481,9 +485,9 @@ class ChangeAtZ(Script):
state = 2
done_layers = 0
if targetL_i > -100000:
modified_gcode += ";ChangeAtZ V%s: reset below Layer %d\n" % (self.version,targetL_i)
modified_gcode += ";ChangeAtZ V%s: reset below Layer %d\n" % (self.version, targetL_i)
else:
modified_gcode += ";ChangeAtZ V%s: reset below %1.2f mm\n" % (self.version,targetZ)
modified_gcode += ";ChangeAtZ V%s: reset below %1.2f mm\n" % (self.version, targetZ)
if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
else: #executes on RepRap, UM2 with Ultigcode and Cura setting

View file

@ -1,10 +1,13 @@
# Cura PostProcessingPlugin
# Author: Amanda de Castilho
# Date: August 28, 2018
# Modified: November 16, 2018 by Joshua Pope-Lewis
# Description: This plugin inserts a line at the start of each layer,
# M117 - displays the filename and layer height to the LCD
# Alternatively, user can override the filename to display alt text + layer height
# Description: This plugin shows custom messages about your print on the Status bar...
# Please look at the 3 options
# - Scolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you arent printing a small item select this option.
# - Name: By default it will use the name generated by Cura (EG: TT_Test_Cube) - Type a custom name in here
# - Max Layer: Enabling this will show how many layers are in the entire print (EG: Layer 1 of 265!)
from ..Script import Script
from UM.Application import Application
@ -15,35 +18,72 @@ class DisplayFilenameAndLayerOnLCD(Script):
def getSettingDataString(self):
return """{
"name": "Display filename and layer on LCD",
"name": "Display Filename And Layer On LCD",
"key": "DisplayFilenameAndLayerOnLCD",
"metadata": {},
"version": 2,
"settings":
{
"scroll":
{
"label": "Scroll enabled/Small layers?",
"description": "If SCROLL_LONG_FILENAMES is enabled select this setting however, if the model is small disable this setting!",
"type": "bool",
"default_value": false
},
"name":
{
"label": "text to display:",
"label": "Text to display:",
"description": "By default the current filename will be displayed on the LCD. Enter text here to override the filename and display something else.",
"type": "str",
"default_value": ""
},
"startNum":
{
"label": "Initial layer number:",
"description": "Choose which number you prefer for the initial layer, 0 or 1",
"type": "int",
"default_value": 0,
"minimum_value": 0,
"maximum_value": 1
},
"maxlayer":
{
"label": "Display max layer?:",
"description": "Display how many layers are in the entire print on status bar?",
"type": "bool",
"default_value": true
}
}
}"""
def execute(self, data):
max_layer = 0
if self.getSettingValueByKey("name") != "":
name = self.getSettingValueByKey("name")
else:
name = Application.getInstance().getPrintInformation().jobName
lcd_text = "M117 " + name + " layer: "
i = 0
name = Application.getInstance().getPrintInformation().jobName
if not self.getSettingValueByKey("scroll"):
if self.getSettingValueByKey("maxlayer"):
lcd_text = "M117 Layer "
else:
lcd_text = "M117 Printing Layer "
else:
lcd_text = "M117 Printing " + name + " - Layer "
i = self.getSettingValueByKey("startNum")
for layer in data:
display_text = lcd_text + str(i)
display_text = lcd_text + str(i) + " " + name
layer_index = data.index(layer)
lines = layer.split("\n")
for line in lines:
if line.startswith(";LAYER_COUNT:"):
max_layer = line
max_layer = max_layer.split(":")[1]
if line.startswith(";LAYER:"):
if self.getSettingValueByKey("maxlayer"):
display_text = display_text + " of " + max_layer
else:
display_text = display_text + "!"
line_index = lines.index(line)
lines.insert(line_index + 1, display_text)
i += 1

View file

@ -0,0 +1,94 @@
# Cura PostProcessingPlugin
# Author: Mathias Lyngklip Kjeldgaard
# Date: July 31, 2019
# Modified: November 26, 2019
# Description: This plugin displayes the remaining time on the LCD of the printer
# using the estimated print-time generated by Cura.
from ..Script import Script
import re
import datetime
class DisplayRemainingTimeOnLCD(Script):
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
"name":"Display Remaining Time on LCD",
"key":"DisplayRemainingTimeOnLCD",
"metadata": {},
"version": 2,
"settings":
{
"TurnOn":
{
"label": "Enable",
"description": "When enabled, It will write Time Left: HHMMSS on the display. This is updated every layer.",
"type": "bool",
"default_value": false
}
}
}"""
def execute(self, data):
if self.getSettingValueByKey("TurnOn"):
total_time = 0
total_time_string = ""
for layer in data:
layer_index = data.index(layer)
lines = layer.split("\n")
for line in lines:
if line.startswith(";TIME:"):
# At this point, we have found a line in the GCODE with ";TIME:"
# which is the indication of total_time. Looks like: ";TIME:1337", where
# 1337 is the total print time in seconds.
line_index = lines.index(line) # We take a hold of that line
split_string = re.split(":", line) # Then we split it, so we can get the number
string_with_numbers = "{}".format(split_string[1]) # Here we insert that number from the
# list into a string.
total_time = int(string_with_numbers) # Only to contert it to a int.
m, s = divmod(total_time, 60) # Math to calculate
h, m = divmod(m, 60) # hours, minutes and seconds.
total_time_string = "{:d}h{:02d}m{:02d}s".format(h, m, s) # Now we put it into the string
lines[line_index] = "M117 Time Left {}".format(total_time_string) # And print that string instead of the original one
elif line.startswith(";TIME_ELAPSED:"):
# As we didnt find the total time (";TIME:"), we have found a elapsed time mark
# This time represents the time the printer have printed. So with some math;
# totalTime - printTime = RemainingTime.
line_index = lines.index(line) # We get a hold of the line
list_split = re.split(":", line) # Again, we split at ":" so we can get the number
string_with_numbers = "{}".format(list_split[1]) # Then we put that number from the list, into a string
current_time = float(string_with_numbers) # This time we convert to a float, as the line looks something like:
# ;TIME_ELAPSED:1234.6789
# which is total time in seconds
time_left = total_time - current_time # Here we calculate remaining time
m1, s1 = divmod(time_left, 60) # And some math to get the total time in seconds into
h1, m1 = divmod(m1, 60) # the right format. (HH,MM,SS)
current_time_string = "{:d}h{:2d}m{:2d}s".format(int(h1), int(m1), int(s1)) # Here we create the string holding our time
lines[line_index] = "M117 Time Left {}".format(current_time_string) # And now insert that into the GCODE
# Here we are OUT of the second for-loop
# Which means we have found and replaces all the occurences.
# Which also means we are ready to join the lines for that section of the GCODE file.
final_lines = "\n".join(lines)
data[layer_index] = final_lines
return data

View file

@ -1,9 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from typing import Optional, Tuple
from UM.Logger import Logger
from typing import List
from ..Script import Script
class FilamentChange(Script):
@ -65,9 +63,10 @@ class FilamentChange(Script):
}
}"""
def execute(self, data: list):
"""data is a list. Each index contains a layer"""
## Inserts the filament change g-code at specific layer numbers.
# \param data A list of layers of g-code.
# \return A similar list, with filament change commands inserted.
def execute(self, data: List[str]):
layer_nums = self.getSettingValueByKey("layer_number")
initial_retract = self.getSettingValueByKey("initial_retract")
later_retract = self.getSettingValueByKey("later_retract")
@ -88,32 +87,16 @@ class FilamentChange(Script):
if y_pos is not None:
color_change = color_change + (" Y%.2f" % y_pos)
color_change = color_change + " ; Generated by FilamentChange plugin"
color_change = color_change + " ; Generated by FilamentChange plugin\n"
layer_targets = layer_nums.split(",")
if len(layer_targets) > 0:
for layer_num in layer_targets:
layer_num = int(layer_num.strip())
if layer_num <= len(data):
index, layer_data = self._searchLayerData(data, layer_num - 1)
if layer_data is None:
Logger.log("e", "Could not found the layer")
continue
lines = layer_data.split("\n")
lines.insert(2, color_change)
final_line = "\n".join(lines)
data[index] = final_line
try:
layer_num = int(layer_num.strip()) + 1 #Needs +1 because the 1st layer is reserved for start g-code.
except ValueError: #Layer number is not an integer.
continue
if 0 < layer_num < len(data):
data[layer_num] = color_change + data[layer_num]
return data
## This method returns the data corresponding with the indicated layer number, looking in the gcode for
# the occurrence of this layer number.
def _searchLayerData(self, data: list, layer_num: int) -> Tuple[int, Optional[str]]:
for index, layer_data in enumerate(data):
first_line = layer_data.split("\n")[0]
# The first line should contain the layer number at the beginning.
if first_line[:len(self._layer_keyword)] == self._layer_keyword:
# If found the layer that we are looking for, then return the data
if first_line[len(self._layer_keyword):] == str(layer_num):
return index, layer_data
return 0, None
return data

View file

@ -0,0 +1,50 @@
# Created by Wayne Porter
from ..Script import Script
class InsertAtLayerChange(Script):
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
"name": "Insert at layer change",
"key": "InsertAtLayerChange",
"metadata": {},
"version": 2,
"settings":
{
"insert_location":
{
"label": "When to insert",
"description": "Whether to insert code before or after layer change.",
"type": "enum",
"options": {"before": "Before", "after": "After"},
"default_value": "before"
},
"gcode_to_add":
{
"label": "GCODE to insert.",
"description": "GCODE to add before or after layer change.",
"type": "str",
"default_value": ""
}
}
}"""
def execute(self, data):
gcode_to_add = self.getSettingValueByKey("gcode_to_add") + "\n"
for layer in data:
# Check that a layer is being printed
lines = layer.split("\n")
for line in lines:
if ";LAYER:" in line:
index = data.index(layer)
if self.getSettingValueByKey("insert_location") == "before":
layer = gcode_to_add + layer
else:
layer = layer + gcode_to_add
data[index] = layer
break
return data

View file

@ -1,15 +1,18 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from ..Script import Script
from UM.Application import Application #To get the current printer's settings.
from UM.Logger import Logger
from typing import List, Tuple
class PauseAtHeight(Script):
def __init__(self):
def __init__(self) -> None:
super().__init__()
def getSettingDataString(self):
def getSettingDataString(self) -> str:
return """{
"name": "Pause at height",
"key": "PauseAtHeight",
@ -105,19 +108,24 @@ class PauseAtHeight(Script):
"standby_temperature":
{
"label": "Standby Temperature",
"description": "Change the temperature during the pause",
"description": "Change the temperature during the pause.",
"unit": "°C",
"type": "int",
"default_value": 0
},
"display_text":
{
"label": "Display Text",
"description": "Text that should appear on the display while paused. If left empty, there will not be any message.",
"type": "str",
"default_value": ""
}
}
}"""
def getNextXY(self, layer: str):
"""
Get the X and Y values for a layer (will be used to get X and Y of
the layer after the pause
"""
## Get the X and Y values for a layer (will be used to get X and Y of the
# layer after the pause).
def getNextXY(self, layer: str) -> Tuple[float, float]:
lines = layer.split("\n")
for line in lines:
if self.getValue(line, "X") is not None and self.getValue(line, "Y") is not None:
@ -126,8 +134,10 @@ class PauseAtHeight(Script):
return x, y
return 0, 0
def execute(self, data: list):
"""data is a list. Each index contains a layer"""
## Inserts the pause commands.
# \param data: List of layers.
# \return New list of layers.
def execute(self, data: List[str]) -> List[str]:
pause_at = self.getSettingValueByKey("pause_at")
pause_height = self.getSettingValueByKey("pause_height")
pause_layer = self.getSettingValueByKey("pause_layer")
@ -143,6 +153,7 @@ class PauseAtHeight(Script):
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
initial_layer_height = Application.getInstance().getGlobalContainerStack().getProperty("layer_height_0", "value")
display_text = self.getSettingValueByKey("display_text")
is_griffin = False
@ -151,6 +162,9 @@ class PauseAtHeight(Script):
# use offset to calculate the current height: <current_height> = <current_z> - <layer_0_z>
layer_0_z = 0
current_z = 0
current_height = 0
current_layer = 0
current_extrusion_f = 0
got_first_g_cmd_on_layer_0 = False
current_t = 0 #Tracks the current extruder for tracking the target temperature.
target_temperature = {} #Tracks the current target temperature for each extruder.
@ -184,6 +198,10 @@ class PauseAtHeight(Script):
if not layers_started:
continue
# Look for the feed rate of an extrusion instruction
if self.getValue(line, "F") is not None and self.getValue(line, "E") is not None:
current_extrusion_f = self.getValue(line, "F")
# If a Z instruction is in the line, read the current Z
if self.getValue(line, "Z") is not None:
current_z = self.getValue(line, "Z")
@ -201,7 +219,7 @@ class PauseAtHeight(Script):
current_height = current_z - layer_0_z
if current_height < pause_height:
break # Try the next layer.
continue # Scan the enitre layer, z-changes are not always on the same/first line.
# Pause at layer
else:
@ -247,8 +265,8 @@ class PauseAtHeight(Script):
# the nozzle)
x, y = self.getNextXY(layer)
prev_lines = prev_layer.split("\n")
for line in prev_lines:
new_e = self.getValue(line, 'E', current_e)
for lin in prev_lines:
new_e = self.getValue(lin, "E", current_e)
if new_e != current_e:
current_e = new_e
break
@ -264,7 +282,7 @@ class PauseAtHeight(Script):
if not is_griffin:
# Retraction
prepend_gcode += self.putValue(M = 83) + "\n"
prepend_gcode += self.putValue(M = 83) + " ; switch to relative E values for any needed retraction\n"
if retraction_amount != 0:
if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves.
retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature.
@ -274,25 +292,28 @@ class PauseAtHeight(Script):
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head away
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + " ; move up a millimeter to get out of the way\n"
# This line should be ok
prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n"
if current_z < 15:
prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n"
prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + " ; too close to bed--move to at least 15mm\n"
if control_temperatures:
# Set extruder standby temperature
prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n"
prepend_gcode += self.putValue(M = 104, S = standby_temperature) + " ; standby temperature\n"
if display_text:
prepend_gcode += "M117 " + display_text + "\n"
# Wait till the user continues printing
prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n"
prepend_gcode += self.putValue(M = 0) + " ; Do the actual pause\n"
if not is_griffin:
if control_temperatures:
# Set extruder resume temperature
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n"
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + " ; resume temperature\n"
# Push the filament back,
if retraction_amount != 0:
@ -308,8 +329,10 @@ class PauseAtHeight(Script):
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head back
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
if current_z < 15:
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n"
prepend_gcode += self.putValue(G = 1, Z = current_z, F = 300) + " ; move back down to resume height\n"
if retraction_amount != 0:
if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves.
retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature.
@ -317,8 +340,13 @@ class PauseAtHeight(Script):
prepend_gcode += self.putValue(G = 11) + "\n"
else:
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
prepend_gcode += self.putValue(G = 1, F = 9000) + "\n"
prepend_gcode += self.putValue(M = 82) + "\n"
if current_extrusion_f != 0:
prepend_gcode += self.putValue(G = 1, F = current_extrusion_f) + " ; restore extrusion feedrate\n"
else:
Logger.log("w", "No previous feedrate found in gcode, feedrate for next layer(s) might be incorrect")
prepend_gcode += self.putValue(M = 82) + " ; switch back to absolute E values\n"
# reset extrude value to pre pause value
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"

View file

@ -1,3 +1,4 @@
from UM.Logger import Logger
from ..Script import Script
class PauseAtHeightforRepetier(Script):
def __init__(self):
@ -73,6 +74,7 @@ class PauseAtHeightforRepetier(Script):
def execute(self, data):
x = 0.
y = 0.
current_extrusion_f = 0
current_z = 0.
pause_z = self.getSettingValueByKey("pause_height")
retraction_amount = self.getSettingValueByKey("retraction_amount")
@ -94,9 +96,11 @@ class PauseAtHeightforRepetier(Script):
if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
current_z = self.getValue(line, 'Z')
if self.getValue(line, 'F') is not None and self.getValue(line, 'E') is not None:
current_extrusion_f = self.getValue(line, 'F', current_extrusion_f)
x = self.getValue(line, 'X', x)
y = self.getValue(line, 'Y', y)
if current_z != None:
if current_z is not None:
if current_z >= pause_z:
index = data.index(layer)
@ -150,7 +154,12 @@ class PauseAtHeightforRepetier(Script):
prepend_gcode +="G1 X%f Y%f F9000\n" % (x, y)
if retraction_amount != 0:
prepend_gcode +="G1 E%f F6000\n" % (retraction_amount)
prepend_gcode +="G1 F9000\n"
if current_extrusion_f != 0:
prepend_gcode += self.putValue(G=1, F=current_extrusion_f) + " ; restore extrusion feedrate\n"
else:
Logger.log("w", "No previous feedrate found in gcode, feedrate for next layer(s) might be incorrect")
prepend_gcode +="M82\n"
# reset extrude value to pre pause value

View file

@ -0,0 +1,75 @@
# Copyright (c) 2019 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
import math
from ..Script import Script
## Continues retracting during all travel moves.
class RetractContinue(Script):
def getSettingDataString(self):
return """{
"name": "Retract Continue",
"key": "RetractContinue",
"metadata": {},
"version": 2,
"settings":
{
"extra_retraction_speed":
{
"label": "Extra Retraction Ratio",
"description": "How much does it retract during the travel move, by ratio of the travel length.",
"type": "float",
"default_value": 0.05
}
}
}"""
def execute(self, data):
current_e = 0
current_x = 0
current_y = 0
extra_retraction_speed = self.getSettingValueByKey("extra_retraction_speed")
for layer_number, layer in enumerate(data):
lines = layer.split("\n")
for line_number, line in enumerate(lines):
if self.getValue(line, "G") in {0, 1}: # Track X,Y location.
current_x = self.getValue(line, "X", current_x)
current_y = self.getValue(line, "Y", current_y)
if self.getValue(line, "G") == 1:
if self.getValue(line, "E"):
new_e = self.getValue(line, "E")
if new_e >= current_e: # Not a retraction.
continue
# A retracted travel move may consist of multiple commands, due to combing.
# This continues retracting over all of these moves and only unretracts at the end.
delta_line = 1
dx = current_x # Track the difference in X for this move only to compute the length of the travel.
dy = current_y
while line_number + delta_line < len(lines) and self.getValue(lines[line_number + delta_line], "G") != 1:
travel_move = lines[line_number + delta_line]
if self.getValue(travel_move, "G") != 0:
delta_line += 1
continue
travel_x = self.getValue(travel_move, "X", dx)
travel_y = self.getValue(travel_move, "Y", dy)
f = self.getValue(travel_move, "F", "no f")
length = math.sqrt((travel_x - dx) * (travel_x - dx) + (travel_y - dy) * (travel_y - dy)) # Length of the travel move.
new_e -= length * extra_retraction_speed # New retraction is by ratio of this travel move.
if f == "no f":
new_travel_move = "G1 X{travel_x} Y{travel_y} E{new_e}".format(travel_x = travel_x, travel_y = travel_y, new_e = new_e)
else:
new_travel_move = "G1 F{f} X{travel_x} Y{travel_y} E{new_e}".format(f = f, travel_x = travel_x, travel_y = travel_y, new_e = new_e)
lines[line_number + delta_line] = new_travel_move
delta_line += 1
dx = travel_x
dy = travel_y
current_e = new_e
new_layer = "\n".join(lines)
data[layer_number] = new_layer
return data

View file

@ -128,9 +128,26 @@ class Stretcher():
onestep = GCodeStep(0, in_relative_movement)
onestep.copyPosFrom(current)
elif _getValue(line, "G") == 1:
last_x = current.step_x
last_y = current.step_y
last_z = current.step_z
last_e = current.step_e
current.readStep(line)
onestep = GCodeStep(1, in_relative_movement)
onestep.copyPosFrom(current)
if (current.step_x == last_x and current.step_y == last_y and
current.step_z == last_z and current.step_e != last_e
):
# It's an extruder only move. Preserve it rather than process it as an
# extruded move. Otherwise, the stretched output might contain slight
# motion in X and Y in addition to E. This can cause problems with
# firmwares that implement pressure advance.
onestep = GCodeStep(-1, in_relative_movement)
onestep.copyPosFrom(current)
# Rather than copy the original line, write a new one with consistent
# extruder coordinates
onestep.comment = "G1 F{} E{}".format(onestep.step_f, onestep.step_e)
else:
onestep = GCodeStep(1, in_relative_movement)
onestep.copyPosFrom(current)
# end of relative movement
elif _getValue(line, "G") == 90:
@ -145,6 +162,7 @@ class Stretcher():
current.readStep(line)
onestep = GCodeStep(-1, in_relative_movement)
onestep.copyPosFrom(current)
onestep.comment = line
else:
onestep = GCodeStep(-1, in_relative_movement)
onestep.copyPosFrom(current)

View file

@ -0,0 +1,95 @@
# Created by Wayne Porter
from ..Script import Script
class TimeLapse(Script):
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
"name": "Time Lapse",
"key": "TimeLapse",
"metadata": {},
"version": 2,
"settings":
{
"trigger_command":
{
"label": "Trigger camera command",
"description": "Gcode command used to trigger camera.",
"type": "str",
"default_value": "M240"
},
"pause_length":
{
"label": "Pause length",
"description": "How long to wait (in ms) after camera was triggered.",
"type": "int",
"default_value": 700,
"minimum_value": 0,
"unit": "ms"
},
"park_print_head":
{
"label": "Park Print Head",
"description": "Park the print head out of the way. Assumes absolute positioning.",
"type": "bool",
"default_value": true
},
"head_park_x":
{
"label": "Park Print Head X",
"description": "What X location does the head move to for photo.",
"unit": "mm",
"type": "float",
"default_value": 0,
"enabled": "park_print_head"
},
"head_park_y":
{
"label": "Park Print Head Y",
"description": "What Y location does the head move to for photo.",
"unit": "mm",
"type": "float",
"default_value": 190,
"enabled": "park_print_head"
},
"park_feed_rate":
{
"label": "Park Feed Rate",
"description": "How fast does the head move to the park coordinates.",
"unit": "mm/s",
"type": "float",
"default_value": 9000,
"enabled": "park_print_head"
}
}
}"""
def execute(self, data):
feed_rate = self.getSettingValueByKey("park_feed_rate")
park_print_head = self.getSettingValueByKey("park_print_head")
x_park = self.getSettingValueByKey("head_park_x")
y_park = self.getSettingValueByKey("head_park_y")
trigger_command = self.getSettingValueByKey("trigger_command")
pause_length = self.getSettingValueByKey("pause_length")
gcode_to_append = ";TimeLapse Begin\n"
if park_print_head:
gcode_to_append += self.putValue(G = 1, F = feed_rate, X = x_park, Y = y_park) + " ;Park print head\n"
gcode_to_append += self.putValue(M = 400) + " ;Wait for moves to finish\n"
gcode_to_append += trigger_command + " ;Snap Photo\n"
gcode_to_append += self.putValue(G = 4, P = pause_length) + " ;Wait for camera\n"
gcode_to_append += ";TimeLapse End\n"
for layer in data:
# Check that a layer is being printed
lines = layer.split("\n")
for line in lines:
if ";LAYER:" in line:
index = data.index(layer)
layer += gcode_to_append
data[index] = layer
break
return data

View file

@ -0,0 +1,46 @@
# Cura PostProcessingPlugin
# Author: Amanda de Castilho
# Date: January 5,2019
# Description: This plugin overrides probing command and inserts code to ensure
# previous probe measurements are loaded and bed leveling enabled
# (searches for G29 and replaces it with M501 & M420 S1)
# *** Assumes G29 is in the start code, will do nothing if it isn't ***
from ..Script import Script
class UsePreviousProbeMeasurements(Script):
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
"name": "Use Previous Probe Measurements",
"key": "UsePreviousProbeMeasurements",
"metadata": {},
"version": 2,
"settings":
{
"use_previous_measurements":
{
"label": "Use last measurement?",
"description": "Selecting this will remove the G29 probing command and instead ensure previous measurements are loaded and enabled",
"type": "bool",
"default_value": false
}
}
}"""
def execute(self, data):
text = "M501 ;load bed level data\nM420 S1 ;enable bed leveling"
if self.getSettingValueByKey("use_previous_measurements"):
for layer in data:
layer_index = data.index(layer)
lines = layer.split("\n")
for line in lines:
if line.startswith("G29"):
line_index = lines.index(line)
lines[line_index] = text
final_lines = "\n".join(lines)
data[layer_index] = final_lines
return data

View file

@ -20,11 +20,19 @@ Item
name: "cura"
}
anchors
{
left: parent.left
right: parent.right
leftMargin: UM.Theme.getSize("wide_margin").width
rightMargin: UM.Theme.getSize("wide_margin").width
}
// Item to ensure that all of the buttons are nicely centered.
Item
{
anchors.horizontalCenter: parent.horizontalCenter
width: openFileButton.width + itemRow.width + UM.Theme.getSize("default_margin").width
width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
height: parent.height
RowLayout
@ -32,9 +40,9 @@ Item
id: itemRow
anchors.left: openFileButton.right
anchors.right: parent.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: Math.round(0.9 * prepareMenu.width)
height: parent.height
spacing: 0
@ -58,6 +66,7 @@ Item
Cura.ConfigurationMenu
{
id: printerSetup
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: itemRow.width - machineSelection.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width

View file

@ -10,7 +10,7 @@ def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Prepare"),
"weight": 0
"weight": 10
}
}

View file

@ -1,8 +1,8 @@
{
"name": "Prepare Stage",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides a prepare stage in Cura.",
"api": "6.0",
"i18n-catalog": "cura"
{
"name": "Prepare Stage",
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides a prepare stage in Cura.",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -11,12 +11,34 @@ import Cura 1.0 as Cura
Item
{
// An Item whose bounds are guaranteed to be safe for overlays to be placed.
// Defaults to parent, ie. the entire available area
property var safeArea: parent
// Subtract the actionPanel from the safe area. This way the view won't draw interface elements under/over it
Item
{
id: childSafeArea
x: safeArea.x - parent.x
y: safeArea.y - parent.y
width: actionPanelWidget.x - x
height: actionPanelWidget.y - y
}
Loader
{
id: previewMain
anchors.fill: parent
source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : ""
onLoaded:
{
if (previewMain.item.safeArea !== undefined){
previewMain.item.safeArea = Qt.binding(function() { return childSafeArea });
}
}
}
Cura.ActionPanelWidget

View file

@ -20,15 +20,21 @@ Item
name: "cura"
}
anchors
{
left: parent.left
right: parent.right
leftMargin: UM.Theme.getSize("wide_margin").width
rightMargin: UM.Theme.getSize("wide_margin").width
}
Row
{
id: stageMenuRow
anchors.centerIn: parent
height: parent.height
width: childrenRect.width
// We want this row to have a preferred with equals to the 85% of the parent
property int preferredWidth: Math.round(0.85 * previewMenu.width)
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
height: parent.height
Cura.ViewsSelector
{
@ -49,12 +55,12 @@ Item
color: UM.Theme.getColor("lining")
}
// This component will grow freely up to complete the preferredWidth of the row.
// This component will grow freely up to complete the width of the row.
Loader
{
id: viewPanel
height: parent.height
width: source != "" ? (stageMenuRow.preferredWidth - viewsSelector.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width) : 0
width: source != "" ? (previewMenu.width - viewsSelector.width - printSetupSelectorItem.width - 2 * (UM.Theme.getSize("wide_margin").width + UM.Theme.getSize("default_lining").width)) : 0
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
}

View file

@ -11,7 +11,7 @@ def getMetaData():
return {
"stage": {
"name": i18n_catalog.i18nc("@item:inmenu", "Preview"),
"weight": 1
"weight": 20
}
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides a preview stage in Cura.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -48,9 +48,13 @@ class WindowsRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
drives = {}
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
# Check possible drive letters, from A to Z
# Check possible drive letters, from C to Z
# Note: using ascii_uppercase because we do not want this to change with locale!
for letter in string.ascii_uppercase:
# Skip A and B, since those drives are typically reserved for floppy disks.
# Those drives can theoretically be reassigned but it's safer to not check them for removable drives.
# Windows will also behave weirdly even with some of its internal functions if you do this (e.g. search indexing doesn't search it).
# Users that have removable drives in A or B will just have to save to file and select the drive there.
for letter in string.ascii_uppercase[2:]:
drive = "{0}:/".format(letter)
# Do we really want to skip A and B?

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"description": "Provides removable drive hotplugging and writing support.",
"version": "1.0.1",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -155,25 +155,19 @@ Item
}
onPositionChanged: parent.onHandleDragged()
onPressed: sliderRoot.setActiveHandle(rangeHandle)
onPressed:
{
sliderRoot.setActiveHandle(rangeHandle)
sliderRoot.forceActiveFocus()
}
}
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.setValueManually // connect callback functions
}
onHeightChanged : {
// After a height change, the pixel-position of the handles is out of sync with the property value
setLowerValue(lowerValue)
setUpperValue(upperValue)
}
// Upper handle
@ -270,11 +264,12 @@ Item
{
id: upperHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - parent.width - width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width, y + height / 2)
visible: sliderRoot.activeHandle == parent
height: sliderRoot.handleSize
anchors.bottom: parent.top
anchors.bottomMargin: UM.Theme.getSize("narrow_margin").height
anchors.horizontalCenter: parent.horizontalCenter
target: Qt.point(parent.width / 2, parent.top)
visible: sliderRoot.activeHandle == parent || sliderRoot.activeHandle == rangeHandle
// custom properties
maximumValue: sliderRoot.maximumValue
@ -333,7 +328,6 @@ Item
// set the slider position based on the lower value
function setValue(value)
{
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
@ -380,11 +374,12 @@ Item
{
id: lowerHandleLabel
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
x: parent.x - parent.width - width
anchors.verticalCenter: parent.verticalCenter
target: Qt.point(sliderRoot.width + width, y + height / 2)
visible: sliderRoot.activeHandle == parent
height: sliderRoot.handleSize
anchors.top: parent.bottom
anchors.topMargin: UM.Theme.getSize("narrow_margin").height
anchors.horizontalCenter: parent.horizontalCenter
target: Qt.point(parent.width / 2, parent.bottom)
visible: sliderRoot.activeHandle == parent || sliderRoot.activeHandle == rangeHandle
// custom properties
maximumValue: sliderRoot.maximumValue
@ -393,4 +388,4 @@ Item
setValue: lowerHandle.setValueManually // connect callback functions
}
}
}
}

View file

@ -3,7 +3,6 @@
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

View file

@ -56,6 +56,11 @@ Item
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
onWidthChanged : {
// After a width change, the pixel-position of the handle is out of sync with the property value
setHandleValue(handleValue)
}
// slider track
Rectangle
{

View file

@ -1,7 +1,6 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick 2.5
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
@ -20,9 +19,9 @@ UM.PointingRectangle {
property int startFrom: 1
target: Qt.point(parent.width, y + height / 2)
arrowSize: UM.Theme.getSize("default_arrow").width
arrowSize: UM.Theme.getSize("button_tooltip_arrow").height
height: parent.height
width: valueLabel.width + UM.Theme.getSize("default_margin").width
width: valueLabel.width
visible: false
color: UM.Theme.getColor("tool_panel_background")
@ -40,26 +39,35 @@ UM.PointingRectangle {
anchors.fill: parent
}
TextMetrics {
id: maxValueMetrics
font: valueLabel.font
text: maximumValue + 1 // layers are 0 based, add 1 for display value
}
TextField {
id: valueLabel
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
alignWhenCentered: false
}
width: ((maximumValue + 1).toString().length + 1) * 10 * screenScaleFactor
width: maxValueMetrics.width + UM.Theme.getSize("default_margin").width
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
horizontalAlignment: TextInput.AlignRight
horizontalAlignment: TextInput.AlignHCenter
// 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")
textColor: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
background: Item { }
renderType: Text.NativeRendering
background: Item { }
}
onEditingFinished: {

View file

@ -48,7 +48,7 @@ if TYPE_CHECKING:
catalog = i18nCatalog("cura")
## View used to display g-code paths.
## The preview layer view. It is used to display g-code paths.
class SimulationView(CuraView):
# Must match SimulationViewMenuComponent.qml
LAYER_VIEW_TYPE_MATERIAL_TYPE = 0
@ -83,9 +83,13 @@ class SimulationView(CuraView):
self._simulationview_composite_shader = None # type: Optional["ShaderProgram"]
self._old_composite_shader = None # type: Optional["ShaderProgram"]
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
self._global_container_stack = None # type: Optional[ContainerStack]
self._proxy = SimulationViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._proxy = None
self._resetSettings()
self._legend_items = None
@ -104,7 +108,6 @@ class SimulationView(CuraView):
Application.getInstance().getPreferences().addPreference("layerview/show_skin", True)
Application.getInstance().getPreferences().addPreference("layerview/show_infill", True)
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._updateWithPreferences()
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
@ -180,8 +183,7 @@ class SimulationView(CuraView):
def _onSceneChanged(self, node: "SceneNode") -> None:
if node.getMeshData() is None:
self.resetLayerData()
return
self.setActivity(False)
self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num)
@ -211,6 +213,8 @@ class SimulationView(CuraView):
def beginRendering(self) -> None:
scene = self.getController().getScene()
renderer = self.getRenderer()
if renderer is None:
return
if not self._ghost_shader:
self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
@ -218,10 +222,10 @@ class SimulationView(CuraView):
if theme is not None:
self._ghost_shader.setUniformValue("u_color", Color(*theme.getColor("layerview_ghost").getRgb()))
for node in DepthFirstIterator(scene.getRoot()): # type: ignore
for node in DepthFirstIterator(scene.getRoot()):
# We do not want to render ConvexHullNode as it conflicts with the bottom layers.
# However, it is somewhat relevant when the node is selected, so do render it then.
if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
if type(node) is ConvexHullNode and not Selection.isSelected(cast(ConvexHullNode, node).getWatchedNode()):
continue
if not node.render(renderer):
@ -384,7 +388,7 @@ class SimulationView(CuraView):
self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness)
try:
self._min_thickness = min(float(p.lineThicknesses[numpy.nonzero(p.lineThicknesses)].min()), self._min_thickness)
except:
except ValueError:
# Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding
# the zero) can't be calculated
Logger.log("i", "Min thickness can't be calculated because all the values are zero")
@ -441,6 +445,8 @@ class SimulationView(CuraView):
## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
# as this caused some issues.
def getProxy(self, engine, script_engine):
if self._proxy is None:
self._proxy = SimulationViewProxy(self)
return self._proxy
def endRendering(self) -> None:
@ -460,6 +466,13 @@ class SimulationView(CuraView):
return True
if event.type == Event.ViewActivateEvent:
# Start listening to changes.
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num)
# FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
# This can happen when you do the following steps:
# 1. Start Cura
@ -479,7 +492,11 @@ class SimulationView(CuraView):
# Make sure the SimulationPass is created
layer_pass = self.getSimulationPass()
self.getRenderer().addRenderPass(layer_pass)
renderer = self.getRenderer()
if renderer is None:
return False
renderer.addRenderPass(layer_pass)
# Make sure the NozzleNode is add to the root
nozzle = self.getNozzleNode()
@ -498,7 +515,7 @@ class SimulationView(CuraView):
self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))
if not self._composite_pass:
self._composite_pass = cast(CompositePass, self.getRenderer().getRenderPass("composite"))
self._composite_pass = cast(CompositePass, renderer.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("simulationview")
@ -506,13 +523,21 @@ class SimulationView(CuraView):
self._composite_pass.setCompositeShader(self._simulationview_composite_shader)
elif event.type == Event.ViewDeactivateEvent:
self._controller.getScene().getRoot().childrenChanged.disconnect(self._onSceneChanged)
Application.getInstance().getPreferences().preferenceChanged.disconnect(self._onPreferencesChanged)
self._wireprint_warning_message.hide()
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
if self._nozzle_node:
self._nozzle_node.setParent(None)
self.getRenderer().removeRenderPass(self._layer_pass)
renderer = self.getRenderer()
if renderer is None:
return False
if self._layer_pass is not None:
renderer.removeRenderPass(self._layer_pass)
if self._composite_pass:
self._composite_pass.setLayerBindings(cast(List[str], self._old_layer_bindings))
self._composite_pass.setCompositeShader(cast(ShaderProgram, self._old_composite_shader))
@ -572,14 +597,14 @@ class SimulationView(CuraView):
self._current_layer_jumps = job.getResult().get("jumps")
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
self._top_layers_job = None # type: Optional["_CreateTopLayersJob"]
self._top_layers_job = None
def _updateWithPreferences(self) -> None:
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
self._compatibility_mode = self._evaluateCompatibilityMode()
self.setSimulationViewType(int(float(Application.getInstance().getPreferences().getValue("layerview/layer_view_type"))));
self.setSimulationViewType(int(float(Application.getInstance().getPreferences().getValue("layerview/layer_view_type"))))
for extruder_nr, extruder_opacity in enumerate(Application.getInstance().getPreferences().getValue("layerview/extruder_opacities").split("|")):
try:

View file

@ -11,19 +11,39 @@ import Cura 1.0 as Cura
Item
{
property bool is_simulation_playing: false
// An Item whose bounds are guaranteed to be safe for overlays to be placed.
// Defaults to parent, ie. the entire available area
// eg. the layer slider will not be placed in this area.
property var safeArea: parent
property bool isSimulationPlaying: false
readonly property real layerSliderSafeYMin: safeArea.y
readonly property real layerSliderSafeYMax: safeArea.y + safeArea.height
readonly property real pathSliderSafeXMin: safeArea.x + playButton.width
readonly property real pathSliderSafeXMax: safeArea.x + safeArea.width
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
// A slider which lets users trace a single layer (XY movements)
PathSlider
{
id: pathSlider
readonly property real preferredWidth: UM.Theme.getSize("slider_layerview_size").height // not a typo, should be as long as layerview slider
readonly property real margin: UM.Theme.getSize("default_margin").width
readonly property real pathSliderSafeWidth: pathSliderSafeXMax - pathSliderSafeXMin
height: UM.Theme.getSize("slider_handle").width
width: UM.Theme.getSize("slider_layerview_size").height
width: preferredWidth + margin * 2 < pathSliderSafeWidth ? preferredWidth : pathSliderSafeWidth - margin * 2
anchors.bottom: parent.bottom
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
anchors.bottomMargin: margin
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: -(parent.width - pathSliderSafeXMax - pathSliderSafeXMin) / 2 // center between parent top and layerSliderSafeYMax
visible: !UM.SimulationView.compatibilityMode
@ -58,7 +78,7 @@ Item
UM.SimpleButton
{
id: playButton
iconSource: !is_simulation_playing ? "./resources/simulation_resume.svg": "./resources/simulation_pause.svg"
iconSource: !isSimulationPlaying ? "./resources/simulation_resume.svg": "./resources/simulation_pause.svg"
width: UM.Theme.getSize("small_button").width
height: UM.Theme.getSize("small_button").height
hoverColor: UM.Theme.getColor("slider_handle_active")
@ -88,7 +108,7 @@ Item
onClicked:
{
if(is_simulation_playing)
if(isSimulationPlaying)
{
pauseSimulation()
}
@ -102,7 +122,7 @@ Item
{
UM.SimulationView.setSimulationRunning(false)
simulationTimer.stop()
is_simulation_playing = false
isSimulationPlaying = false
layerSlider.manuallyChanged = true
pathSlider.manuallyChanged = true
}
@ -131,7 +151,7 @@ Item
// 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 (!is_simulation_playing)
if (!isSimulationPlaying)
{
if (currentPath >= numPaths)
{
@ -166,22 +186,30 @@ Item
}
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
// correctly, because part of the logic is in this trigger function.
is_simulation_playing = true
isSimulationPlaying = true
}
}
// Scrolls trough Z layers
LayerSlider
{
property var preferredHeight: UM.Theme.getSize("slider_layerview_size").height
property double heightMargin: UM.Theme.getSize("default_margin").height * 3 // extra margin to accomodate layer number tooltips
property double layerSliderSafeHeight: layerSliderSafeYMax - layerSliderSafeYMin
id: layerSlider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("slider_layerview_size").height
height: preferredHeight + heightMargin * 2 < layerSliderSafeHeight ? preferredHeight : layerSliderSafeHeight - heightMargin * 2
anchors
{
right: parent.right
verticalCenter: parent.verticalCenter
verticalCenterOffset: -(parent.height - layerSliderSafeYMax - layerSliderSafeYMin) / 2 // center between parent top and layerSliderSafeYMax
rightMargin: UM.Theme.getSize("default_margin").width
bottomMargin: heightMargin
topMargin: heightMargin
}
// Custom properties

View file

@ -15,6 +15,8 @@ Cura.ExpandableComponent
{
id: base
dragPreferencesNamePrefix: "view/colorscheme"
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
Connections
@ -177,7 +179,6 @@ Cura.ExpandableComponent
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
width: parent.width
visible: !UM.SimulationView.compatibilityMode
enabled: index < 4
onClicked:
{

View file

@ -1,21 +1,24 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
import SimulationView
if TYPE_CHECKING:
from .SimulationView import SimulationView
class SimulationViewProxy(QObject):
def __init__(self, parent=None):
def __init__(self, simulation_view: "SimulationView", parent=None):
super().__init__(parent)
self._simulation_view = simulation_view
self._current_layer = 0
self._controller = Application.getInstance().getController()
self._controller.activeViewChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
self.is_simulationView_selected = False
self._onActiveViewChanged()
currentLayerChanged = pyqtSignal()
currentPathChanged = pyqtSignal()
@ -28,182 +31,112 @@ class SimulationViewProxy(QObject):
@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
return self._simulation_view.getActivity()
@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
return self._simulation_view.getMaxLayers()
@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
return self._simulation_view.getCurrentLayer()
@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
return self._simulation_view.getMinimumLayer()
@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
return self._simulation_view.getMaxPaths()
@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
return self._simulation_view.getCurrentPath()
@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
return self._simulation_view.getMinimumPath()
@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
return self._simulation_view.isBusy()
@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
return self._simulation_view.getCompatibilityMode()
@pyqtProperty(int, notify=globalStackChanged)
def extruderCount(self):
return self._simulation_view.getExtruderCount()
@pyqtSlot(int)
def setCurrentLayer(self, layer_num):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setLayer(layer_num)
self._simulation_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)
self._simulation_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)
self._simulation_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)
self._simulation_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.setSimulationViewType(layer_view_type)
self._simulation_view.setSimulationViewType(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
return self._simulation_view.getSimulationViewType()
@pyqtSlot(bool)
def setSimulationRunning(self, running):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
active_view.setSimulationRunning(running)
self._simulation_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
return self._simulation_view.isSimulationRunning()
@pyqtSlot(result=float)
def getMinFeedrate(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinFeedrate()
return 0
return self._simulation_view.getMinFeedrate()
@pyqtSlot(result=float)
def getMaxFeedrate(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxFeedrate()
return 0
return self._simulation_view.getMaxFeedrate()
@pyqtSlot(result=float)
def getMinThickness(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMinThickness()
return 0
return self._simulation_view.getMinThickness()
@pyqtSlot(result=float)
def getMaxThickness(self):
active_view = self._controller.getActiveView()
if isinstance(active_view, SimulationView.SimulationView.SimulationView):
return active_view.getMaxThickness()
return 0
return self._simulation_view.getMaxThickness()
# 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)
self._simulation_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)
self._simulation_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)
self._simulation_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)
self._simulation_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
self._simulation_view.setShowInfill(show)
def _layerActivityChanged(self):
self.activityChanged.emit()
@ -236,24 +169,25 @@ class SimulationViewProxy(QObject):
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)
if active_view == self._simulation_view:
self._simulation_view.currentLayerNumChanged.connect(self._onLayerChanged)
self._simulation_view.currentPathNumChanged.connect(self._onPathChanged)
self._simulation_view.maxLayersChanged.connect(self._onMaxLayersChanged)
self._simulation_view.maxPathsChanged.connect(self._onMaxPathsChanged)
self._simulation_view.busyChanged.connect(self._onBusyChanged)
self._simulation_view.activityChanged.connect(self._onActivityChanged)
self._simulation_view.globalStackChanged.connect(self._onGlobalStackChanged)
self._simulation_view.preferencesChanged.connect(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)
elif self.is_simulationView_selected:
# Disconnect all of em again.
self.is_simulationView_selected = False
self._simulation_view.currentLayerNumChanged.disconnect(self._onLayerChanged)
self._simulation_view.currentPathNumChanged.disconnect(self._onPathChanged)
self._simulation_view.maxLayersChanged.disconnect(self._onMaxLayersChanged)
self._simulation_view.maxPathsChanged.disconnect(self._onMaxPathsChanged)
self._simulation_view.busyChanged.disconnect(self._onBusyChanged)
self._simulation_view.activityChanged.disconnect(self._onActivityChanged)
self._simulation_view.globalStackChanged.disconnect(self._onGlobalStackChanged)
self._simulation_view.preferencesChanged.disconnect(self._onPreferencesChanged)

View file

@ -1,6 +1,9 @@
[shaders]
vertex =
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
@ -16,7 +19,7 @@ vertex =
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
// shade the color depending on the extruder index
v_color = a_color;
// 8 and 9 are travel moves
@ -76,7 +79,10 @@ fragment =
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
@ -92,7 +98,7 @@ vertex41core =
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
v_color = a_color;
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);
@ -154,7 +160,9 @@ u_show_skin = 1
u_show_infill = 1
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix
u_viewMatrix = view_matrix
u_projectionMatrix = projection_matrix
[attributes]
a_vertex = vertex

View file

@ -1,10 +1,10 @@
[shaders]
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_max_feedrate;
uniform lowp float u_min_feedrate;
@ -104,7 +104,10 @@ vertex41core =
geometry41core =
#version 410
uniform highp mat4 u_viewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
@ -136,6 +139,8 @@ geometry41core =
void main()
{
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
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
@ -183,65 +188,83 @@ geometry41core =
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
// 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));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head);
//And reverse so that the line is also visible from the back side.
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[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[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[1], v_color[1], g_vertex_normal_vert, vb_up);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
EndPrimitive();
} else {
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz);
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz);
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert);
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert);
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz);
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz);
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert);
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert);
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head);
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head);
// 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));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_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));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_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));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_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));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_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));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
EndPrimitive();
}
@ -301,9 +324,9 @@ u_min_thickness = 0
u_max_thickness = 1
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix
u_viewProjectionMatrix = view_projection_matrix
u_viewMatrix = view_matrix
u_projectionMatrix = projection_matrix
u_normalMatrix = normal_matrix
u_lightPosition = light_0_position

View file

@ -1,10 +1,10 @@
[shaders]
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewProjectionMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp vec4 u_extruder_opacity; // currently only for max 4 extruders, others always visible
@ -58,7 +58,10 @@ vertex41core =
geometry41core =
#version 410
uniform highp mat4 u_viewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform int u_show_travel_moves;
uniform int u_show_helpers;
uniform int u_show_skin;
@ -90,6 +93,8 @@ geometry41core =
void main()
{
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
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
@ -137,65 +142,83 @@ geometry41core =
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
// 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));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head);
//And reverse so that the line is also visible from the back side.
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[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[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[1], v_color[1], g_vertex_normal_vert, vb_up);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
EndPrimitive();
} else {
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz);
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz);
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert);
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert);
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz);
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz);
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert);
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert);
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head);
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head);
// 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));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_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));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_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));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_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));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_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));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
EndPrimitive();
}
@ -246,9 +269,9 @@ 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_viewMatrix = view_matrix
u_projectionMatrix = projection_matrix
u_normalMatrix = normal_matrix
u_lightPosition = light_0_position

View file

@ -1,6 +1,9 @@
[shaders]
vertex =
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
@ -16,7 +19,7 @@ vertex =
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * 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
@ -80,7 +83,10 @@ fragment =
vertex41core =
#version 410
uniform highp mat4 u_modelViewProjectionMatrix;
uniform highp mat4 u_modelMatrix;
uniform highp mat4 u_viewMatrix;
uniform highp mat4 u_projectionMatrix;
uniform lowp float u_active_extruder;
uniform lowp float u_shade_factor;
uniform highp int u_layer_view_type;
@ -96,7 +102,7 @@ vertex41core =
void main()
{
gl_Position = u_modelViewProjectionMatrix * a_vertex;
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * 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);
@ -159,7 +165,9 @@ u_show_skin = 1
u_show_infill = 1
[bindings]
u_modelViewProjectionMatrix = model_view_projection_matrix
u_modelMatrix = model_matrix
u_viewMatrix = view_matrix
u_projectionMatrix = projection_matrix
[attributes]
a_vertex = vertex

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.1",
"description": "Provides the Simulation view.",
"api": "6.0",
"api": "7.0",
"i18n-catalog": "cura"
}

View file

@ -1,150 +1,156 @@
// Copyright (c) 2018 Ultimaker B.V.
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
import Cura 1.1 as Cura
UM.Dialog
Window
{
UM.I18nCatalog { id: catalog; name: "cura" }
id: baseDialog
title: catalog.i18nc("@title:window", "More information on anonymous data collection")
visible: false
modality: Qt.ApplicationModal
minimumWidth: 500 * screenScaleFactor
minimumHeight: 400 * screenScaleFactor
width: minimumWidth
height: minimumHeight
property bool allowSendData: true // for saving the user's choice
color: UM.Theme.getColor("main_background")
onAccepted: manager.setSendSliceInfo(allowSendData)
property bool allowSendData: true // for saving the user's choice
onVisibilityChanged:
{
if (visible)
{
baseDialog.allowSendData = UM.Preferences.getValue("info/send_slice_info");
baseDialog.allowSendData = UM.Preferences.getValue("info/send_slice_info")
if (baseDialog.allowSendData)
{
allowSendButton.checked = true;
allowSendButton.checked = true
}
else
{
dontSendButton.checked = true;
dontSendButton.checked = true
}
}
}
// Main content area
Item
{
id: textRow
anchors
{
top: parent.top
bottom: radioButtonsRow.top
bottomMargin: UM.Theme.getSize("default_margin").height
left: parent.left
right: parent.right
}
anchors.fill: parent
anchors.margins: UM.Theme.getSize("default_margin").width
Label
Item // Text part
{
id: headerText
id: textRow
anchors
{
top: parent.top
left: parent.left
right: parent.right
}
text: catalog.i18nc("@text:window", "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent.")
wrapMode: Text.WordWrap
}
TextArea
{
id: exampleData
anchors
{
top: headerText.bottom
topMargin: UM.Theme.getSize("default_margin").height
bottom: parent.bottom
bottom: radioButtonsRow.top
bottomMargin: UM.Theme.getSize("default_margin").height
left: parent.left
right: parent.right
}
text: manager.getExampleData()
readOnly: true
textFormat: TextEdit.PlainText
}
}
Column
{
id: radioButtonsRow
width: parent.width
anchors.bottom: buttonRow.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
ExclusiveGroup { id: group }
RadioButton
{
id: dontSendButton
text: catalog.i18nc("@text:window", "I don't want to send these data")
exclusiveGroup: group
onClicked:
Label
{
baseDialog.allowSendData = !checked;
id: headerText
anchors
{
top: parent.top
left: parent.left
right: parent.right
}
text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
wrapMode: Text.WordWrap
renderType: Text.NativeRendering
}
}
RadioButton
{
id: allowSendButton
text: catalog.i18nc("@text:window", "Allow sending these data to Ultimaker and help us improve Cura")
exclusiveGroup: group
onClicked:
Cura.ScrollableTextArea
{
baseDialog.allowSendData = checked;
}
}
}
anchors
{
top: headerText.bottom
topMargin: UM.Theme.getSize("default_margin").height
bottom: parent.bottom
bottomMargin: UM.Theme.getSize("default_margin").height
left: parent.left
right: parent.right
}
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", "OK")
onClicked:
{
baseDialog.accepted()
baseDialog.hide()
textArea.text: manager.getExampleData()
textArea.textFormat: Text.RichText
textArea.wrapMode: Text.Wrap
textArea.readOnly: true
}
}
Button
Column // Radio buttons for agree and disagree
{
id: radioButtonsRow
anchors.left: parent.left
text: catalog.i18nc("@action:button", "Cancel")
onClicked:
anchors.right: parent.right
anchors.bottom: buttonRow.top
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
Cura.RadioButton
{
baseDialog.rejected()
baseDialog.hide()
id: dontSendButton
text: catalog.i18nc("@text:window", "I don't want to send anonymous data")
onClicked:
{
baseDialog.allowSendData = !checked
}
}
Cura.RadioButton
{
id: allowSendButton
text: catalog.i18nc("@text:window", "Allow sending anonymous data")
onClicked:
{
baseDialog.allowSendData = checked
}
}
}
Item // Bottom buttons
{
id: buttonRow
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: childrenRect.height
Cura.PrimaryButton
{
anchors.right: parent.right
text: catalog.i18nc("@action:button", "OK")
onClicked:
{
manager.setSendSliceInfo(allowSendData)
baseDialog.hide()
}
}
Cura.SecondaryButton
{
anchors.left: parent.left
text: catalog.i18nc("@action:button", "Cancel")
onClicked:
{
baseDialog.hide()
}
}
}
}

View file

@ -18,6 +18,8 @@ from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.Qt.Duration import DurationFormat
from cura import ApplicationMetadata
from .SliceInfoJob import SliceInfoJob
@ -48,20 +50,6 @@ class SliceInfo(QObject, Extension):
def _onAppInitialized(self):
# DO NOT read any preferences values in the constructor because at the time plugins are created, no version
# upgrade has been performed yet because version upgrades are plugins too!
if not self._application.getPreferences().getValue("info/asked_send_slice_info"):
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."),
lifetime = 0,
dismissable = False,
title = catalog.i18nc("@info:title", "Collecting Data"))
self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None,
description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK)
self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None,
description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."))
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
self.send_slice_info_message.show()
if self._more_info_dialog is None:
self._more_info_dialog = self._createDialog("MoreInfoWindow.qml")
@ -76,7 +64,7 @@ class SliceInfo(QObject, Extension):
def showMoreInfoDialog(self):
if self._more_info_dialog is None:
self._more_info_dialog = self._createDialog("MoreInfoWindow.qml")
self._more_info_dialog.open()
self._more_info_dialog.show()
def _createDialog(self, qml_name):
Logger.log("d", "Creating dialog [%s]", qml_name)
@ -91,7 +79,7 @@ class SliceInfo(QObject, Extension):
if not plugin_path:
Logger.log("e", "Could not get plugin path!", self.getPluginId())
return None
file_path = os.path.join(plugin_path, "example_data.json")
file_path = os.path.join(plugin_path, "example_data.html")
if file_path:
with open(file_path, "r", encoding = "utf-8") as f:
self._example_data_content = f.read()
@ -133,6 +121,7 @@ class SliceInfo(QObject, Extension):
data["time_stamp"] = time.time()
data["schema_version"] = 0
data["cura_version"] = application.getVersion()
data["cura_build_type"] = ApplicationMetadata.CuraBuildType
active_mode = Application.getInstance().getPreferences().getValue("cura/active_mode")
if active_mode == 0:
@ -140,6 +129,10 @@ class SliceInfo(QObject, Extension):
else:
data["active_mode"] = "custom"
data["camera_view"] = application.getPreferences().getValue("general/camera_perspective_mode")
if data["camera_view"] == "orthographic":
data["camera_view"] = "orthogonal" #The database still only recognises the old name "orthogonal".
definition_changes = global_stack.definitionChanges
machine_settings_changed_by_user = False
if definition_changes.getId() != "empty":
@ -184,6 +177,7 @@ class SliceInfo(QObject, Extension):
extruder_dict["extruder_settings"] = extruder_settings
data["extruders"].append(extruder_dict)
data["intent_category"] = global_stack.getIntentCategory()
data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type")
data["user_modified_setting_keys"] = self._getUserModifiedSettingKeys()
@ -195,6 +189,8 @@ class SliceInfo(QObject, Extension):
model = dict()
model["hash"] = node.getMeshData().getHash()
bounding_box = node.getBoundingBox()
if not bounding_box:
continue
model["bounding_box"] = {"minimum": {"x": bounding_box.minimum.x,
"y": bounding_box.minimum.y,
"z": bounding_box.minimum.z},

View file

@ -8,6 +8,8 @@ import ssl
import urllib.request
import urllib.error
import certifi
class SliceInfoJob(Job):
def __init__(self, url, data):
@ -20,11 +22,14 @@ class SliceInfoJob(Job):
Logger.log("e", "URL or DATA for sending slice info was not set!")
return
# Submit data
kwoptions = {"data" : self._data, "timeout" : 5}
# CURA-6698 Create an SSL context and use certifi CA certificates for verification.
context = ssl.SSLContext(protocol = ssl.PROTOCOL_TLSv1_2)
context.load_verify_locations(cafile = certifi.where())
if Platform.isOSX():
kwoptions["context"] = ssl._create_unverified_context()
# Submit data
kwoptions = {"data": self._data,
"timeout": 5,
"context": context}
Logger.log("i", "Sending anonymous slice info to [%s]...", self._url)
@ -35,4 +40,4 @@ class SliceInfoJob(Job):
except urllib.error.HTTPError:
Logger.logException("e", "An HTTP error occurred while trying to send slice information")
except Exception: # We don't want any exception to cause problems
Logger.logException("e", "An exception occurred while trying to send slice information")
Logger.logException("e", "An exception occurred while trying to send slice information")

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