mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-02 20:52:20 -07:00
Merge branch 'master' into log_litho
This commit is contained in:
commit
2e43f63adb
2016 changed files with 69835 additions and 51810 deletions
|
|
@ -19,12 +19,12 @@ 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
|
||||
|
||||
|
||||
try:
|
||||
|
|
@ -131,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()
|
||||
|
|
|
|||
|
|
@ -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,22 +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
|
||||
|
|
@ -59,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
|
||||
|
|
@ -79,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.
|
||||
|
|
@ -227,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:
|
||||
|
|
@ -253,12 +261,15 @@ 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 = ""
|
||||
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
|
||||
|
|
@ -316,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:
|
||||
|
|
@ -341,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
|
||||
|
|
@ -397,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
|
||||
|
|
@ -419,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
|
||||
|
|
@ -495,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
|
||||
|
|
@ -515,6 +542,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._dialog.setNumVisibleSettings(num_visible_settings)
|
||||
self._dialog.setQualityName(quality_name)
|
||||
self._dialog.setQualityType(quality_type)
|
||||
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)
|
||||
|
|
@ -558,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")
|
||||
|
||||
|
|
@ -648,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.
|
||||
|
|
@ -674,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.
|
||||
|
|
@ -697,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)
|
||||
|
|
@ -727,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:
|
||||
|
|
@ -737,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"
|
||||
|
|
@ -753,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())
|
||||
|
|
@ -787,11 +794,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
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())
|
||||
|
|
@ -817,10 +823,9 @@ 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
|
||||
container.setDirty(True)
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
for key, value in container_info.parser["values"].items():
|
||||
|
|
@ -828,7 +833,47 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
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()
|
||||
|
|
@ -887,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
|
||||
|
|
@ -932,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
|
||||
|
|
@ -959,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():
|
||||
|
|
@ -973,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)
|
||||
|
|
@ -1008,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/", "")
|
||||
|
|
@ -1020,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)
|
||||
|
||||
|
|
@ -1041,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:
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class WorkspaceDialog(QObject):
|
|||
self._quality_name = ""
|
||||
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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2019 fieldOfView
|
||||
# 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:
|
||||
|
|
@ -39,9 +39,9 @@ class AMFReader(MeshReader):
|
|||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name="application/x-amf",
|
||||
comment="AMF",
|
||||
suffixes=["amf"]
|
||||
name = "application/x-amf",
|
||||
comment = "AMF",
|
||||
suffixes = ["amf"]
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ class AMFReader(MeshReader):
|
|||
if t.tag == "x":
|
||||
v[0] = float(t.text) * scale
|
||||
elif t.tag == "y":
|
||||
v[2] = float(t.text) * scale
|
||||
v[2] = -float(t.text) * scale
|
||||
elif t.tag == "z":
|
||||
v[1] = float(t.text) * scale
|
||||
amf_mesh_vertices.append(v)
|
||||
|
|
@ -114,7 +114,7 @@ class AMFReader(MeshReader):
|
|||
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 = 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()
|
||||
|
|
@ -123,7 +123,7 @@ class AMFReader(MeshReader):
|
|||
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.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())
|
||||
|
||||
|
|
@ -165,9 +165,9 @@ class AMFReader(MeshReader):
|
|||
indices.append(face)
|
||||
face_count += 1
|
||||
|
||||
vertices = numpy.asarray(vertices, dtype=numpy.float32)
|
||||
indices = numpy.asarray(indices, dtype=numpy.int32)
|
||||
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)
|
||||
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals)
|
||||
return mesh_data
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
"author": "fieldOfView",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading AMF files.",
|
||||
"api": "6.0.0"
|
||||
"api": "7.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -369,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
|
||||
|
|
@ -400,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)
|
||||
|
|
@ -438,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)
|
||||
|
|
@ -460,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")
|
||||
|
|
@ -543,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):
|
||||
|
|
@ -560,6 +570,10 @@ 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()
|
||||
|
|
@ -631,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))
|
||||
|
|
@ -670,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:
|
||||
|
|
@ -811,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)
|
||||
|
||||
|
|
@ -822,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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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,8 +210,9 @@ 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:
|
||||
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
|
||||
|
|
@ -210,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()
|
||||
|
|
@ -261,10 +278,14 @@ class StartSliceJob(Job):
|
|||
|
||||
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]
|
||||
|
||||
|
|
@ -288,7 +309,7 @@ class StartSliceJob(Job):
|
|||
|
||||
obj.vertices = flat_verts
|
||||
|
||||
self._handlePerObjectSettings(object, obj)
|
||||
self._handlePerObjectSettings(cast(CuraSceneNode, object), obj)
|
||||
|
||||
Job.yieldThread()
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import math
|
||||
import re
|
||||
from typing import Dict, List, NamedTuple, Optional, Union
|
||||
from typing import Dict, List, NamedTuple, Optional, Union, Set
|
||||
|
||||
import numpy
|
||||
|
||||
|
|
@ -38,6 +38,8 @@ class FlavorParser:
|
|||
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
|
||||
|
|
@ -66,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:
|
||||
|
|
@ -292,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
|
||||
|
|
@ -418,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()
|
||||
|
||||
|
|
@ -453,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)
|
||||
|
||||
|
|
@ -467,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 settings is not None and 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")
|
||||
|
|
|
|||
|
|
@ -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,8 +11,14 @@ 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
|
||||
class GCodeReader(MeshReader):
|
||||
|
|
@ -30,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)
|
||||
|
||||
|
|
@ -54,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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ 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 os.path
|
||||
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 +15,13 @@ 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 = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ import UM.i18n
|
|||
from UM.FlameProfiler import pyqtSlot
|
||||
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
|
||||
|
||||
|
|
@ -40,6 +43,9 @@ class MachineSettingsAction(MachineAction):
|
|||
self._backend = self._application.getBackend()
|
||||
self.onFinished.connect(self._onFinished)
|
||||
|
||||
# If the g-code flavour changes between UltiGCode and another flavour, we need to update the container tree.
|
||||
self._application.globalContainerStackChanged.connect(self._updateHasMaterialsInContainerTree)
|
||||
|
||||
# Which container index in a stack to store machine setting changes.
|
||||
@pyqtProperty(int, constant = True)
|
||||
def storeContainerIndex(self) -> int:
|
||||
|
|
@ -50,6 +56,18 @@ class MachineSettingsAction(MachineAction):
|
|||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
||||
self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||
|
||||
## 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):
|
||||
global_stack = self._application.getMachineManager().activeMachine
|
||||
if not global_stack:
|
||||
|
|
@ -92,16 +110,13 @@ class MachineSettingsAction(MachineAction):
|
|||
return
|
||||
|
||||
definition = global_stack.getDefinition()
|
||||
if definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False):
|
||||
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(global_stack.extruders.keys())
|
||||
has_materials = global_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
||||
|
||||
material_node = None
|
||||
if has_materials:
|
||||
global_stack.setMetaDataEntry("has_materials", True)
|
||||
else:
|
||||
|
|
@ -110,11 +125,15 @@ class MachineSettingsAction(MachineAction):
|
|||
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(global_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()
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ Item
|
|||
Cura.NumericTextFieldWithUnit // "Nozzle size"
|
||||
{
|
||||
id: extruderNozzleSizeField
|
||||
visible: !Cura.MachineManager.hasVariants
|
||||
visible: !Cura.MachineManager.activeMachine.hasVariants
|
||||
containerStackId: base.extruderStackId
|
||||
settingKey: "machine_nozzle_size"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
|
|
@ -107,6 +107,7 @@ Item
|
|||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
allowNegativeValue: true
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +122,7 @@ Item
|
|||
labelWidth: base.labelWidth
|
||||
controlWidth: base.controlWidth
|
||||
unitText: catalog.i18nc("@label", "mm")
|
||||
allowNegativeValue: true
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
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
|
||||
|
|
@ -16,40 +17,37 @@ Item
|
|||
id: base
|
||||
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
property int labelWidth: 120 * 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 machineStackId: Cura.MachineManager.activeMachineId
|
||||
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
|
||||
|
||||
Item
|
||||
RowLayout
|
||||
{
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: base.columnWidth
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
spacing: base.columnSpacing
|
||||
|
||||
|
|
@ -59,6 +57,8 @@ Item
|
|||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "X (Width)"
|
||||
|
|
@ -140,6 +140,18 @@ Item
|
|||
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
|
||||
|
|
@ -163,9 +175,8 @@ Item
|
|||
// =======================================
|
||||
Column
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
width: base.columnWidth
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
spacing: base.columnSpacing
|
||||
|
||||
|
|
@ -175,6 +186,8 @@ Item
|
|||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Cura.PrintHeadMinMaxTextField // "X min"
|
||||
|
|
@ -191,6 +204,8 @@ Item
|
|||
|
||||
axisName: "x"
|
||||
axisMinOrMax: "min"
|
||||
allowNegativeValue: true
|
||||
allowPositiveValue: false
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
|
@ -209,6 +224,8 @@ Item
|
|||
|
||||
axisName: "y"
|
||||
axisMinOrMax: "min"
|
||||
allowNegativeValue: true
|
||||
allowPositiveValue: false
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
|
@ -227,6 +244,8 @@ Item
|
|||
|
||||
axisName: "x"
|
||||
axisMinOrMax: "max"
|
||||
allowNegativeValue: false
|
||||
allowPositiveValue: true
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
|
@ -247,6 +266,8 @@ Item
|
|||
|
||||
axisName: "y"
|
||||
axisMinOrMax: "max"
|
||||
allowNegativeValue: false
|
||||
allowPositiveValue: true
|
||||
|
||||
forceUpdateOnChangeFunction: forceUpdateFunction
|
||||
}
|
||||
|
|
@ -313,22 +334,23 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
Item // Start and End G-code
|
||||
RowLayout // 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
|
||||
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"
|
||||
{
|
||||
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
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
labelText: catalog.i18nc("@title:label", "Start G-code")
|
||||
containerStackId: machineStackId
|
||||
|
|
@ -338,11 +360,8 @@ Item
|
|||
|
||||
Cura.GcodeTextArea // "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
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
labelText: catalog.i18nc("@title:label", "End G-code")
|
||||
containerStackId: machineStackId
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,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
|
||||
|
||||
|
|
@ -131,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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Rectangle
|
|||
{
|
||||
// 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++)
|
||||
|
|
@ -97,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
|
||||
|
|
@ -166,4 +166,4 @@ Rectangle
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ def getMetaData():
|
|||
return {
|
||||
"stage": {
|
||||
"name": i18n_catalog.i18nc("@item:inmenu", "Monitor"),
|
||||
"weight": 2
|
||||
"weight": 30
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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 { }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
139
plugins/PerObjectSettingsTool/SettingPickDialog.qml
Normal file
139
plugins/PerObjectSettingsTool/SettingPickDialog.qml
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
# Cura PostProcessingPlugin
|
||||
# Author: Mathias Lyngklip Kjeldgaard
|
||||
# Date: July 31, 2019
|
||||
# Modified: ---
|
||||
|
||||
# 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":"Disaplay 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",
|
||||
"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
|
||||
|
|
@ -92,8 +92,11 @@ class FilamentChange(Script):
|
|||
layer_targets = layer_nums.split(",")
|
||||
if len(layer_targets) > 0:
|
||||
for layer_num in layer_targets:
|
||||
layer_num = int(layer_num.strip()) + 1
|
||||
if layer_num <= len(data):
|
||||
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
|
||||
|
|
@ -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")
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
75
plugins/PostProcessingPlugin/scripts/RetractContinue.py
Normal file
75
plugins/PostProcessingPlugin/scripts/RetractContinue.py
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -77,10 +77,10 @@ class TimeLapse(Script):
|
|||
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 += 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
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ def getMetaData():
|
|||
return {
|
||||
"stage": {
|
||||
"name": i18n_catalog.i18nc("@item:inmenu", "Prepare"),
|
||||
"weight": 0
|
||||
"weight": 10
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -11,12 +11,30 @@ import Cura 1.0 as Cura
|
|||
|
||||
Item
|
||||
{
|
||||
|
||||
// Subtract the actionPanel from the safe area. This way the view won't draw interface elements under/over it
|
||||
Item {
|
||||
id: safeArea
|
||||
visible: false
|
||||
anchors.left: parent.left
|
||||
anchors.right: actionPanelWidget.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: actionPanelWidget.top
|
||||
}
|
||||
|
||||
Loader
|
||||
{
|
||||
id: previewMain
|
||||
anchors.fill: parent
|
||||
|
||||
source: UM.Controller.activeView != null && UM.Controller.activeView.mainComponent != null ? UM.Controller.activeView.mainComponent : ""
|
||||
|
||||
Binding
|
||||
{
|
||||
target: previewMain.item
|
||||
property: "safeArea"
|
||||
value:safeArea
|
||||
}
|
||||
}
|
||||
|
||||
Cura.ActionPanelWidget
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ def getMetaData():
|
|||
return {
|
||||
"stage": {
|
||||
"name": i18n_catalog.i18nc("@item:inmenu", "Preview"),
|
||||
"weight": 1
|
||||
"weight": 20
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,6 +176,11 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
onHeightChanged : {
|
||||
// After a height change, the pixel-position of the lower handle is out of sync with the property value
|
||||
setLowerValue(lowerValue)
|
||||
}
|
||||
|
||||
// Upper handle
|
||||
Rectangle
|
||||
{
|
||||
|
|
@ -333,7 +338,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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -218,10 +220,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 +386,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 +443,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 +464,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
|
||||
|
|
@ -506,6 +517,8 @@ 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:
|
||||
|
|
@ -572,14 +585,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:
|
||||
|
|
|
|||
|
|
@ -11,9 +11,18 @@ 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 var layerSliderSafeYMax: safeArea.y + safeArea.height
|
||||
|
||||
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
||||
|
||||
// A slider which lets users trace a single layer (XY movements)
|
||||
PathSlider
|
||||
{
|
||||
id: pathSlider
|
||||
|
|
@ -58,7 +67,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 +97,7 @@ Item
|
|||
|
||||
onClicked:
|
||||
{
|
||||
if(is_simulation_playing)
|
||||
if(isSimulationPlaying)
|
||||
{
|
||||
pauseSimulation()
|
||||
}
|
||||
|
|
@ -102,7 +111,7 @@ Item
|
|||
{
|
||||
UM.SimulationView.setSimulationRunning(false)
|
||||
simulationTimer.stop()
|
||||
is_simulation_playing = false
|
||||
isSimulationPlaying = false
|
||||
layerSlider.manuallyChanged = true
|
||||
pathSlider.manuallyChanged = true
|
||||
}
|
||||
|
|
@ -131,7 +140,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 +175,28 @@ 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
|
||||
id: layerSlider
|
||||
|
||||
width: UM.Theme.getSize("slider_handle").width
|
||||
height: UM.Theme.getSize("slider_layerview_size").height
|
||||
height: preferredHeight + heightMargin * 2 < layerSliderSafeYMax ? preferredHeight : layerSliderSafeYMax - heightMargin * 2
|
||||
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
verticalCenterOffset: -(parent.height - layerSliderSafeYMax) / 2 // center between parent top and layerSliderSafeYMax
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
bottomMargin: heightMargin
|
||||
topMargin: heightMargin
|
||||
}
|
||||
|
||||
// Custom properties
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,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":
|
||||
|
|
@ -170,6 +174,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()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<b>Operating System:</b> Windows 10<br/>
|
||||
<b>Language:</b> en_US<br/>
|
||||
<b>Machine Type:</b> Ultimaker S5<br/>
|
||||
<b>Intent Profile:</b> Default<br/>
|
||||
<b>Quality Profile:</b> Fast<br/>
|
||||
<b>Using Custom Settings:</b> No
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||
"api": "6.0",
|
||||
"api": "7.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,9 +57,12 @@ class SolidView(View):
|
|||
# As the rendering is called a *lot* we really, dont want to re-evaluate the property every time. So we store em!
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
|
||||
support_angle_stack = global_container_stack.extruders.get(str(support_extruder_nr))
|
||||
if support_angle_stack:
|
||||
support_extruder_nr = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))
|
||||
try:
|
||||
support_angle_stack = global_container_stack.extruderList[support_extruder_nr]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
self._support_angle = support_angle_stack.getProperty("support_angle", "value")
|
||||
|
||||
def beginRendering(self):
|
||||
|
|
@ -139,6 +142,10 @@ class SolidView(View):
|
|||
shade_factor * int(material_color[5:7], 16) / 255,
|
||||
1.0
|
||||
]
|
||||
|
||||
# Color the currently selected face-id. (Disable for now.)
|
||||
#face = Selection.getHoverFace()
|
||||
uniforms["hover_face"] = -1 #if not face or node != face[0] else face[1]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides a normal solid mesh view.",
|
||||
"api": "6.0",
|
||||
"api": "7.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -98,8 +98,10 @@ class SupportEraser(Tool):
|
|||
|
||||
node.setName("Eraser")
|
||||
node.setSelectable(True)
|
||||
node.setCalculateBoundingBox(True)
|
||||
mesh = self._createCube(10)
|
||||
node.setMeshData(mesh.build())
|
||||
node.calculateBoundingBoxMesh()
|
||||
|
||||
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
||||
"api": "6.0",
|
||||
"api": "7.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||
<?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>support_blocker</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Cura-Icon" fill="#000000">
|
||||
<path d="M19,27 L12,27 L3,27 L3,3 L12,3 L12,11 L19,11 L19,19 L27,19 L27,27 L19,27 Z M4,4 L4,26 L11,26 L11,4 L4,4 Z" id="Combined-Shape"></path>
|
||||
<polygon id="Path" points="10 17.1441441 9.18918919 17.954955 7.52252252 16.3333333 5.85585586 18 5.04504505 17.1891892 6.66666667 15.4774775 5 13.8558559 5.81081081 13.045045 7.52252252 14.6666667 9.18918919 13 10 13.8108108 8.33333333 15.4774775"></polygon>
|
||||
<g id="support_blocker" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-3" transform="translate(3.000000, 2.000000)">
|
||||
<g id="Print-as-support-Copy" fill="#000">
|
||||
<path d="M0.833,19.65 L0.833,22.342 L0,22.3428571 L0.833,19.65 Z M4.166,8.879 L4.166,22.342 L2.499,22.342 L2.499,14.266 L4.166,8.879 Z M7.5,0.8 L7.5,22.342 L5.833,22.342 L5.833,0.8 L7.5,0.8 Z M10.833,0.8 L10.833,22.342 L9.166,22.342 L9.166,0.8 L10.833,0.8 Z M14.166,0.8 L14.166,14 L12.499,14 L12.499,0.8 L14.166,0.8 Z M15.833,8.879 L17.418,14 L15.833,14 L15.833,8.879 Z M4.166,0.8 L4.166,6.139 L2.499,1.351 L2.499,0.8 L4.166,0.8 Z M17.5,0.8 L17.5,1.351 L15.833,6.139 L15.833,0.8 L17.5,0.8 Z" id="Combined-Shape"></path>
|
||||
</g>
|
||||
<path d="M12,14 L22,14 L22,24 L12,24 L12,14 Z" id="Rectangle" stroke="#000" stroke-dasharray="1,1"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 866 B After Width: | Height: | Size: 1.2 KiB |
|
|
@ -2,6 +2,6 @@
|
|||
"name": "Toolbox",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"api": "6.0",
|
||||
"api": "7.0",
|
||||
"description": "Find, manage and install new Cura packages."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,32 +48,32 @@ Window
|
|||
ToolboxLoadingPage
|
||||
{
|
||||
id: viewLoading
|
||||
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "loading"
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "loading"
|
||||
}
|
||||
ToolboxErrorPage
|
||||
{
|
||||
id: viewErrored
|
||||
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "errored"
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "errored"
|
||||
}
|
||||
ToolboxDownloadsPage
|
||||
{
|
||||
id: viewDownloads
|
||||
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "overview"
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "overview"
|
||||
}
|
||||
ToolboxDetailPage
|
||||
{
|
||||
id: viewDetail
|
||||
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "detail"
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "detail"
|
||||
}
|
||||
ToolboxAuthorPage
|
||||
{
|
||||
id: viewAuthor
|
||||
visible: toolbox.viewCategory != "installed" && toolbox.viewPage == "author"
|
||||
visible: toolbox.viewCategory !== "installed" && toolbox.viewPage === "author"
|
||||
}
|
||||
ToolboxInstalledPage
|
||||
{
|
||||
id: installedPluginList
|
||||
visible: toolbox.viewCategory == "installed"
|
||||
visible: toolbox.viewCategory === "installed"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
|
|
@ -11,48 +11,17 @@ Item
|
|||
id: base
|
||||
|
||||
property var packageData
|
||||
property var technicalDataSheetUrl:
|
||||
{
|
||||
var link = undefined
|
||||
if ("Technical Data Sheet" in packageData.links)
|
||||
{
|
||||
// HACK: This is the way the old API (used in 3.6-beta) used to do it. For safety it's still here,
|
||||
// but it can be removed over time.
|
||||
link = packageData.links["Technical Data Sheet"]
|
||||
}
|
||||
else if ("technicalDataSheet" in packageData.links)
|
||||
{
|
||||
link = packageData.links["technicalDataSheet"]
|
||||
}
|
||||
return link
|
||||
}
|
||||
property var safetyDataSheetUrl:
|
||||
{
|
||||
var sds_name = "safetyDataSheet"
|
||||
return (sds_name in packageData.links) ? packageData.links[sds_name] : undefined
|
||||
}
|
||||
property var printingGuidelinesUrl:
|
||||
{
|
||||
var pg_name = "printingGuidelines"
|
||||
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
|
||||
}
|
||||
property var technicalDataSheetUrl: packageData.links.technicalDataSheet
|
||||
property var safetyDataSheetUrl: packageData.links.safetyDataSheet
|
||||
property var printingGuidelinesUrl: packageData.links.printingGuidelines
|
||||
property var materialWebsiteUrl: packageData.links.website
|
||||
|
||||
property var materialWebsiteUrl:
|
||||
{
|
||||
var pg_name = "website"
|
||||
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
|
||||
}
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
height: visible ? childrenRect.height : 0
|
||||
height: childrenRect.height
|
||||
onVisibleChanged: packageData.type === "material" && (compatibilityItem.visible || dataSheetLinks.visible)
|
||||
|
||||
visible: packageData.type == "material" &&
|
||||
(packageData.has_configs || technicalDataSheetUrl !== undefined ||
|
||||
safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined ||
|
||||
materialWebsiteUrl !== undefined)
|
||||
|
||||
Item
|
||||
Column
|
||||
{
|
||||
id: combatibilityItem
|
||||
id: compatibilityItem
|
||||
visible: packageData.has_configs
|
||||
width: parent.width
|
||||
// This is a bit of a hack, but the whole QML is pretty messy right now. This needs a big overhaul.
|
||||
|
|
@ -61,7 +30,6 @@ Item
|
|||
Label
|
||||
{
|
||||
id: heading
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@label", "Compatibility")
|
||||
wrapMode: Text.WordWrap
|
||||
|
|
@ -73,8 +41,6 @@ Item
|
|||
TableView
|
||||
{
|
||||
id: table
|
||||
anchors.top: heading.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
frameVisible: false
|
||||
|
||||
|
|
@ -155,32 +121,32 @@ Item
|
|||
TableViewColumn
|
||||
{
|
||||
role: "machine"
|
||||
title: "Machine"
|
||||
title: catalog.i18nc("@label:table_header", "Machine")
|
||||
width: Math.floor(table.width * 0.25)
|
||||
delegate: columnTextDelegate
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "print_core"
|
||||
title: "Print Core"
|
||||
title: "Print Core" //This term should not be translated.
|
||||
width: Math.floor(table.width * 0.2)
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "build_plate"
|
||||
title: "Build Plate"
|
||||
title: catalog.i18nc("@label:table_header", "Build Plate")
|
||||
width: Math.floor(table.width * 0.225)
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "support_material"
|
||||
title: "Support"
|
||||
title: catalog.i18nc("@label:table_header", "Support")
|
||||
width: Math.floor(table.width * 0.225)
|
||||
}
|
||||
TableViewColumn
|
||||
{
|
||||
role: "quality"
|
||||
title: "Quality"
|
||||
title: catalog.i18nc("@label:table_header", "Quality")
|
||||
width: Math.floor(table.width * 0.1)
|
||||
}
|
||||
}
|
||||
|
|
@ -188,13 +154,14 @@ Item
|
|||
|
||||
Label
|
||||
{
|
||||
id: data_sheet_links
|
||||
anchors.top: combatibilityItem.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height / 2
|
||||
id: dataSheetLinks
|
||||
anchors.top: compatibilityItem.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("narrow_margin").height
|
||||
visible: base.technicalDataSheetUrl !== undefined ||
|
||||
base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined ||
|
||||
base.materialWebsiteUrl !== undefined
|
||||
height: visible ? contentHeight : 0
|
||||
base.safetyDataSheetUrl !== undefined ||
|
||||
base.printingGuidelinesUrl !== undefined ||
|
||||
base.materialWebsiteUrl !== undefined
|
||||
|
||||
text:
|
||||
{
|
||||
var result = ""
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
|
|
@ -11,10 +10,9 @@ Item
|
|||
id: detailList
|
||||
ScrollView
|
||||
{
|
||||
frameVisible: false
|
||||
clip: true
|
||||
anchors.fill: detailList
|
||||
style: UM.Theme.styles.scrollview
|
||||
flickableItem.flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
Column
|
||||
{
|
||||
anchors
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ Item
|
|||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Your rating") + ":"
|
||||
visible: details.type == "plugin"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: tile
|
||||
width: detailList.width - UM.Theme.getSize("wide_margin").width
|
||||
height: normalData.height + compatibilityChart.height + 4 * UM.Theme.getSize("default_margin").height
|
||||
Item
|
||||
height: normalData.height + 2 * UM.Theme.getSize("wide_margin").height
|
||||
Column
|
||||
{
|
||||
id: normalData
|
||||
height: childrenRect.height
|
||||
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: controls.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width * 2 + UM.Theme.getSize("toolbox_loader").width
|
||||
top: parent.top
|
||||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: packageName
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("toolbox_property_label").height
|
||||
text: model.name
|
||||
|
|
@ -33,9 +33,9 @@ Item
|
|||
font: UM.Theme.getFont("medium_bold")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors.top: packageName.bottom
|
||||
width: parent.width
|
||||
text: model.description
|
||||
maximumLineCount: 25
|
||||
|
|
@ -45,6 +45,12 @@ Item
|
|||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
ToolboxCompatibilityChart
|
||||
{
|
||||
width: parent.width
|
||||
packageData: model
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxDetailTileActions
|
||||
|
|
@ -57,20 +63,12 @@ Item
|
|||
packageData: model
|
||||
}
|
||||
|
||||
ToolboxCompatibilityChart
|
||||
{
|
||||
id: compatibilityChart
|
||||
anchors.top: normalData.bottom
|
||||
width: normalData.width
|
||||
packageData: model
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("lining")
|
||||
width: tile.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
anchors.top: compatibilityChart.bottom
|
||||
anchors.top: normalData.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height + UM.Theme.getSize("wide_margin").height //Normal margin for spacing after chart, wide margin between items.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Column
|
|||
// Don't allow installing while another download is running
|
||||
enabled: installed || (!(toolbox.isDownloading && toolbox.activePackage != model) && !loginRequired)
|
||||
opacity: enabled ? 1.0 : 0.5
|
||||
visible: !updateButton.visible && !installed// Don't show when the update button is visible
|
||||
visible: !updateButton.visible && !installed // Don't show when the update button is visible
|
||||
}
|
||||
|
||||
Cura.SecondaryButton
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
Column
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import UM 1.1 as UM
|
||||
|
||||
ScrollView
|
||||
{
|
||||
frameVisible: false
|
||||
clip: true
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
style: UM.Theme.styles.scrollview
|
||||
|
||||
flickableItem.flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: mainColumn.height
|
||||
|
||||
Column
|
||||
{
|
||||
id: mainColumn
|
||||
width: base.width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
height: childrenRect.height
|
||||
|
||||
ToolboxDownloadsShowcase
|
||||
{
|
||||
|
|
@ -31,14 +28,14 @@ ScrollView
|
|||
{
|
||||
id: allPlugins
|
||||
width: parent.width
|
||||
heading: toolbox.viewCategory == "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins")
|
||||
model: toolbox.viewCategory == "material" ? toolbox.materialsAvailableModel : toolbox.pluginsAvailableModel
|
||||
heading: toolbox.viewCategory === "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins")
|
||||
model: toolbox.viewCategory === "material" ? toolbox.materialsAvailableModel : toolbox.pluginsAvailableModel
|
||||
}
|
||||
|
||||
ToolboxDownloadsGrid
|
||||
{
|
||||
id: genericMaterials
|
||||
visible: toolbox.viewCategory == "material"
|
||||
visible: toolbox.viewCategory === "material"
|
||||
width: parent.width
|
||||
heading: catalog.i18nc("@label", "Generic Materials")
|
||||
model: toolbox.materialsGenericModel
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: page
|
||||
frameVisible: false
|
||||
clip: true
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
style: UM.Theme.styles.scrollview
|
||||
flickableItem.flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
Column
|
||||
{
|
||||
width: page.width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
padding: UM.Theme.getSize("wide_margin").width
|
||||
visible: toolbox.pluginsInstalledModel.items.length > 0
|
||||
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
|
||||
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
left: parent.left
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
}
|
||||
height: childrenRect.height + 2 * UM.Theme.getSize("wide_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
width: page.width
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
text: catalog.i18nc("@title:tab", "Plugins")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("large")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
id: installedPlugins
|
||||
color: "transparent"
|
||||
width: parent.width
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
|
|
@ -65,8 +65,15 @@ ScrollView
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
text: catalog.i18nc("@title:tab", "Materials")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("medium")
|
||||
|
|
@ -75,9 +82,14 @@ ScrollView
|
|||
|
||||
Rectangle
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: parent.padding
|
||||
}
|
||||
id: installedMaterials
|
||||
color: "transparent"
|
||||
width: parent.width
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ Item
|
|||
Column
|
||||
{
|
||||
id: pluginInfo
|
||||
topPadding: Math.floor(UM.Theme.getSize("default_margin").height / 2)
|
||||
topPadding: UM.Theme.getSize("narrow_margin").height
|
||||
property var color: model.type === "plugin" && !isEnabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("text")
|
||||
width: Math.floor(tileRow.width - (authorInfo.width + pluginActions.width + 2 * tileRow.spacing + ((disableButton.visible) ? disableButton.width + tileRow.spacing : 0)))
|
||||
Label
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ UM.Dialog
|
|||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
text: licenseDialog.pluginName + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
|
||||
text: licenseDialog.pluginName + ": " + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?")
|
||||
wrapMode: Text.Wrap
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Copyright (c) 2019 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Item
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: base
|
||||
id: button
|
||||
|
||||
property var active: false
|
||||
property var complete: false
|
||||
|
|
@ -25,43 +25,36 @@ Item
|
|||
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
height: UM.Theme.getSize("toolbox_action_button").height
|
||||
|
||||
Cura.PrimaryButton
|
||||
fixedWidthMode: true
|
||||
text:
|
||||
{
|
||||
id: button
|
||||
width: UM.Theme.getSize("toolbox_action_button").width
|
||||
height: UM.Theme.getSize("toolbox_action_button").height
|
||||
fixedWidthMode: true
|
||||
text:
|
||||
if (complete)
|
||||
{
|
||||
if (complete)
|
||||
{
|
||||
return completeLabel
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
return activeLabel
|
||||
}
|
||||
else
|
||||
{
|
||||
return readyLabel
|
||||
}
|
||||
return completeLabel
|
||||
}
|
||||
onClicked:
|
||||
else if (active)
|
||||
{
|
||||
if (complete)
|
||||
{
|
||||
completeAction()
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
activeAction()
|
||||
}
|
||||
else
|
||||
{
|
||||
readyAction()
|
||||
}
|
||||
return activeLabel
|
||||
}
|
||||
else
|
||||
{
|
||||
return readyLabel
|
||||
}
|
||||
busy: active
|
||||
}
|
||||
onClicked:
|
||||
{
|
||||
if (complete)
|
||||
{
|
||||
completeAction()
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
activeAction()
|
||||
}
|
||||
else
|
||||
{
|
||||
readyAction()
|
||||
}
|
||||
}
|
||||
busy: active
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import json
|
||||
|
|
@ -19,6 +19,7 @@ from UM.Version import Version
|
|||
from cura import ApplicationMetadata
|
||||
from cura import UltimakerCloudAuthentication
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
|
||||
from .AuthorsModel import AuthorsModel
|
||||
from .PackagesModel import PackagesModel
|
||||
|
|
@ -359,15 +360,22 @@ class Toolbox(QObject, Extension):
|
|||
@pyqtSlot()
|
||||
def resetMaterialsQualitiesAndUninstall(self) -> None:
|
||||
application = CuraApplication.getInstance()
|
||||
material_manager = application.getMaterialManager()
|
||||
quality_manager = application.getQualityManager()
|
||||
machine_manager = application.getMachineManager()
|
||||
container_tree = ContainerTree.getInstance()
|
||||
|
||||
for global_stack, extruder_nr, container_id in self._package_used_materials:
|
||||
default_material_node = material_manager.getDefaultMaterial(global_stack, extruder_nr, global_stack.extruders[extruder_nr].variant.getName())
|
||||
extruder = global_stack.extruderList[int(extruder_nr)]
|
||||
approximate_diameter = extruder.getApproximateMaterialDiameter()
|
||||
variant_node = container_tree.machines[global_stack.definition.getId()].variants[extruder.variant.getName()]
|
||||
default_material_node = variant_node.preferredMaterial(approximate_diameter)
|
||||
machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack)
|
||||
for global_stack, extruder_nr, container_id in self._package_used_qualities:
|
||||
default_quality_group = quality_manager.getDefaultQualityType(global_stack)
|
||||
variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
|
||||
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
|
||||
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
|
||||
definition_id = global_stack.definition.getId()
|
||||
machine_node = container_tree.machines[definition_id]
|
||||
default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type]
|
||||
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
|
||||
|
||||
if self._package_id_to_uninstall is not None:
|
||||
|
|
@ -655,8 +663,12 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
# Check if the download was sucessfull
|
||||
if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
|
||||
return
|
||||
try:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.logException("w", "Failed to download package and failed to parse a response from it")
|
||||
finally:
|
||||
return
|
||||
# Must not delete the temporary file on Windows
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||
file_path = self._temp_plugin_file.name
|
||||
|
|
|
|||
161
plugins/TrimeshReader/TrimeshReader.py
Normal file
161
plugins/TrimeshReader/TrimeshReader.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V., fieldOfView
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# The _toMeshData function is taken from the AMFReader class which was built by fieldOfView.
|
||||
|
||||
from typing import Any, List, Union, TYPE_CHECKING
|
||||
import numpy # To create the mesh data.
|
||||
import os.path # To create the mesh name for the resulting mesh.
|
||||
import trimesh # To load the files into a Trimesh.
|
||||
|
||||
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices # To construct meshes from the Trimesh data.
|
||||
from UM.Mesh.MeshReader import MeshReader # The plug-in type we're extending.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType # To add file types that we can open.
|
||||
from UM.Scene.GroupDecorator import GroupDecorator # Added to the parent node if we load multiple nodes at once.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator # Added to the resulting scene node.
|
||||
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator # Added to group nodes if we load multiple nodes at once.
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode # To create a node in the scene after reading the file.
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator # Added to the resulting scene node.
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
## Class that leverages Trimesh to import files.
|
||||
class TrimeshReader(MeshReader):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._supported_extensions = [".ctm", ".dae", ".gltf", ".glb", ".ply", ".zae"]
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ctm",
|
||||
comment = "Open Compressed Triangle Mesh",
|
||||
suffixes = ["ctm"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/vnd.collada+xml",
|
||||
comment = "COLLADA Digital Asset Exchange",
|
||||
suffixes = ["dae"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/gltf-binary",
|
||||
comment = "glTF Binary",
|
||||
suffixes = ["glb"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/gltf+json",
|
||||
comment = "glTF Embedded JSON",
|
||||
suffixes = ["gltf"]
|
||||
)
|
||||
)
|
||||
# Trimesh seems to have a bug when reading .off files.
|
||||
#MimeTypeDatabase.addMimeType(
|
||||
# MimeType(
|
||||
# name = "application/x-off",
|
||||
# comment = "Geomview Object File Format",
|
||||
# suffixes = ["off"]
|
||||
# )
|
||||
#)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "application/x-ply", # Wikipedia lists the MIME type as "text/plain" but that won't do as it's not unique to PLY files.
|
||||
comment = "Stanford Triangle Format",
|
||||
suffixes = ["ply"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name = "model/vnd.collada+xml+zip",
|
||||
comment = "Compressed COLLADA Digital Asset Exchange",
|
||||
suffixes = ["zae"]
|
||||
)
|
||||
)
|
||||
|
||||
## Reads a file using Trimesh.
|
||||
# \param file_name The file path. This is assumed to be one of the file
|
||||
# types that Trimesh can read. It will not be checked again.
|
||||
# \return A scene node that contains the file's contents.
|
||||
def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
|
||||
# CURA-6739
|
||||
# GLTF files are essentially JSON files. If you directly give a file name to trimesh.load(), it will
|
||||
# try to figure out the format, but for GLTF, it loads it as a binary file with flags "rb", and the json.load()
|
||||
# doesn't like it. For some reason, this seems to happen with 3.5.7, but not 3.7.1. Below is a workaround to
|
||||
# pass a file object that has been opened with "r" instead "rb" to load a GLTF file.
|
||||
if file_name.lower().endswith(".gltf"):
|
||||
mesh_or_scene = trimesh.load(open(file_name, "r", encoding = "utf-8"), file_type = "gltf")
|
||||
else:
|
||||
mesh_or_scene = trimesh.load(file_name)
|
||||
|
||||
meshes = [] # type: List[Union[trimesh.Trimesh, trimesh.Scene, Any]]
|
||||
if isinstance(mesh_or_scene, trimesh.Trimesh):
|
||||
meshes = [mesh_or_scene]
|
||||
elif isinstance(mesh_or_scene, trimesh.Scene):
|
||||
meshes = [mesh for mesh in mesh_or_scene.geometry.values()]
|
||||
|
||||
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
nodes = [] # type: List[SceneNode]
|
||||
for mesh in meshes:
|
||||
if not isinstance(mesh, trimesh.Trimesh): # Trimesh can also receive point clouds, 2D paths, 3D paths or metadata. Skip those.
|
||||
continue
|
||||
mesh.merge_vertices()
|
||||
mesh.remove_unreferenced_vertices()
|
||||
mesh.fix_normals()
|
||||
mesh_data = self._toMeshData(mesh)
|
||||
|
||||
file_base_name = os.path.basename(file_name)
|
||||
new_node = CuraSceneNode()
|
||||
new_node.setMeshData(mesh_data)
|
||||
new_node.setSelectable(True)
|
||||
new_node.setName(file_base_name if len(meshes) == 1 else "{file_base_name} {counter}".format(file_base_name = file_base_name, counter = str(len(nodes) + 1)))
|
||||
new_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
new_node.addDecorator(SliceableObjectDecorator())
|
||||
nodes.append(new_node)
|
||||
|
||||
if len(nodes) == 1:
|
||||
return nodes[0]
|
||||
# Add all nodes to a group so they stay together.
|
||||
group_node = CuraSceneNode()
|
||||
group_node.addDecorator(GroupDecorator())
|
||||
group_node.addDecorator(ConvexHullDecorator())
|
||||
group_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
for node in nodes:
|
||||
node.setParent(group_node)
|
||||
return group_node
|
||||
|
||||
## Converts a Trimesh to Uranium's MeshData.
|
||||
# \param tri_node A Trimesh containing the contents of a file that was
|
||||
# just read.
|
||||
# \return Mesh data from the Trimesh in a way that Uranium can understand
|
||||
# it.
|
||||
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
|
||||
46
plugins/TrimeshReader/__init__.py
Normal file
46
plugins/TrimeshReader/__init__.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Copyright (c) 2019 Ultimaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import TrimeshReader
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("uranium")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"mesh_reader": [
|
||||
{
|
||||
"extension": "ctm",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox 'Open' is part of the name of this file format.", "Open Compressed Triangle Mesh")
|
||||
},
|
||||
{
|
||||
"extension": "dae",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "COLLADA Digital Asset Exchange")
|
||||
},
|
||||
{
|
||||
"extension": "glb",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "glTF Binary")
|
||||
},
|
||||
{
|
||||
"extension": "gltf",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "glTF Embedded JSON")
|
||||
},
|
||||
# Trimesh seems to have a bug when reading OFF files.
|
||||
#{
|
||||
# "extension": "off",
|
||||
# "description": i18n_catalog.i18nc("@item:inlistbox", "Geomview Object File Format")
|
||||
#},
|
||||
{
|
||||
"extension": "ply",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Stanford Triangle Format")
|
||||
},
|
||||
{
|
||||
"extension": "zae",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Compressed COLLADA Digital Asset Exchange")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return {"mesh_reader": TrimeshReader.TrimeshReader()}
|
||||
7
plugins/TrimeshReader/plugin.json
Normal file
7
plugins/TrimeshReader/plugin.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "Trimesh Reader",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading model files.",
|
||||
"api": "7.0.0"
|
||||
}
|
||||
|
|
@ -39,4 +39,4 @@ class UFPReader(MeshReader):
|
|||
# Open the GCodeReader to parse the data
|
||||
gcode_reader = PluginRegistry.getInstance().getPluginObject("GCodeReader") # type: ignore
|
||||
gcode_reader.preReadFromStream(gcode_stream) # type: ignore
|
||||
return gcode_reader.readFromStream(gcode_stream) # type: ignore
|
||||
return gcode_reader.readFromStream(gcode_stream, file_name) # type: ignore
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for reading Ultimaker Format Packages.",
|
||||
"supported_sdk_versions": ["6.0.0"],
|
||||
"supported_sdk_versions": ["7.0.0"],
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
from typing import cast
|
||||
|
|
@ -7,13 +7,13 @@ from Charon.VirtualFile import VirtualFile #To open UFP files.
|
|||
from Charon.OpenMode import OpenMode #To indicate that we want to write to UFP files.
|
||||
from io import StringIO #For converting g-code to bytes.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Mesh.MeshWriter import MeshWriter #The writer we need to implement.
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
||||
from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
|
||||
from PyQt5.QtCore import QBuffer
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Snapshot import Snapshot
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
||||
|
|
@ -38,7 +38,11 @@ class UFPWriter(MeshWriter):
|
|||
def _createSnapshot(self, *args):
|
||||
# must be called from the main thread because of OpenGL
|
||||
Logger.log("d", "Creating thumbnail image...")
|
||||
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
||||
try:
|
||||
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
||||
except Exception:
|
||||
Logger.logException("w", "Failed to create snapshot image")
|
||||
self._snapshot = None # Failing to create thumbnail should not fail creation of UFP
|
||||
|
||||
# This needs to be called on the main thread (Qt thread) because the serialization of material containers can
|
||||
# trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the
|
||||
|
|
@ -79,9 +83,9 @@ class UFPWriter(MeshWriter):
|
|||
Logger.log("d", "Thumbnail not created, cannot save it")
|
||||
|
||||
# Store the material.
|
||||
application = Application.getInstance()
|
||||
application = CuraApplication.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
material_manager = application.getMaterialManager()
|
||||
container_registry = application.getContainerRegistry()
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
material_extension = "xml.fdm_material"
|
||||
|
|
@ -107,12 +111,12 @@ class UFPWriter(MeshWriter):
|
|||
continue
|
||||
|
||||
material_root_id = material.getMetaDataEntry("base_file")
|
||||
material_group = material_manager.getMaterialGroup(material_root_id)
|
||||
if material_group is None:
|
||||
Logger.log("e", "Cannot find material container with root id [%s]", material_root_id)
|
||||
material_root_query = container_registry.findContainers(id = material_root_id)
|
||||
if not material_root_query:
|
||||
Logger.log("e", "Cannot find material container with root id {root_id}".format(root_id = material_root_id))
|
||||
return False
|
||||
material_container = material_root_query[0]
|
||||
|
||||
material_container = material_group.root_material_node.getContainer()
|
||||
try:
|
||||
serialized_material = material_container.serialize()
|
||||
except NotImplementedError:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.1",
|
||||
"description": "Provides support for writing Ultimaker Format Packages.",
|
||||
"api": "6.0",
|
||||
"api": "7.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from .src import DiscoverUM3Action
|
||||
from .src import UM3OutputDevicePlugin
|
||||
from .src import UltimakerNetworkedPrinterAction
|
||||
|
||||
|
||||
def getMetaData():
|
||||
|
|
@ -11,5 +11,5 @@ def getMetaData():
|
|||
def register(app):
|
||||
return {
|
||||
"output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(),
|
||||
"machine_action": DiscoverUM3Action.DiscoverUM3Action()
|
||||
"machine_action": UltimakerNetworkedPrinterAction.UltimakerNetworkedPrinterAction()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "UM3 Network Connection",
|
||||
"name": "Ultimaker Network Connection",
|
||||
"author": "Ultimaker B.V.",
|
||||
"description": "Manages network connections to Ultimaker 3 printers.",
|
||||
"version": "1.0.1",
|
||||
"api": "6.0",
|
||||
"description": "Manages network connections to Ultimaker networked printers.",
|
||||
"version": "2.0.0",
|
||||
"api": "7.0",
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue