mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-06 21:44:01 -06:00
Merge remote-tracking branch 'origin/master' into CURA-3710_setting_visibility_preset
This commit is contained in:
commit
0e5c67a38f
167 changed files with 27305 additions and 16782 deletions
|
@ -31,10 +31,42 @@ import zipfile
|
|||
import io
|
||||
import configparser
|
||||
import os
|
||||
import threading
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
#
|
||||
# HACK:
|
||||
#
|
||||
# In project loading, when override the existing machine is selected, the stacks and containers that are correctly
|
||||
# active in the system will be overridden at runtime. Because the project loading is done in a different thread than
|
||||
# the Qt thread, something else can kick in the middle of the process. One of them is the rendering. It will access
|
||||
# the current stacks and container, which have not completely been updated yet, so Cura will crash in this case.
|
||||
#
|
||||
# This "@call_on_qt_thread" decorator makes sure that a function will always be called on the Qt thread (blocking).
|
||||
# It is applied to the read() function of project loading so it can be guaranteed that only after the project loading
|
||||
# process is completely done, everything else that needs to occupy the QT thread will be executed.
|
||||
#
|
||||
class InterCallObject:
|
||||
def __init__(self):
|
||||
self.finish_event = threading.Event()
|
||||
self.result = None
|
||||
|
||||
|
||||
def call_on_qt_thread(func):
|
||||
def _call_on_qt_thread_wrapper(*args, **kwargs):
|
||||
def _handle_call(ico, *args, **kwargs):
|
||||
ico.result = func(*args, **kwargs)
|
||||
ico.finish_event.set()
|
||||
inter_call_object = InterCallObject()
|
||||
new_args = tuple([inter_call_object] + list(args)[:])
|
||||
CuraApplication.getInstance().callLater(_handle_call, *new_args, **kwargs)
|
||||
inter_call_object.finish_event.wait()
|
||||
return inter_call_object.result
|
||||
return _call_on_qt_thread_wrapper
|
||||
|
||||
|
||||
## Base implementation for reading 3MF workspace files.
|
||||
class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
def __init__(self):
|
||||
|
@ -401,6 +433,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# containing global.cfg / extruder.cfg
|
||||
#
|
||||
# \param file_name
|
||||
@call_on_qt_thread
|
||||
def read(self, file_name):
|
||||
archive = zipfile.ZipFile(file_name, "r")
|
||||
|
||||
|
@ -526,6 +559,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
|
||||
user_instance_containers = []
|
||||
quality_and_definition_changes_instance_containers = []
|
||||
quality_changes_instance_containers = []
|
||||
for instance_container_file in instance_container_files:
|
||||
container_id = self._stripFileToId(instance_container_file)
|
||||
serialized = archive.open(instance_container_file).read().decode("utf-8")
|
||||
|
@ -631,6 +665,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# The ID already exists, but nothing in the values changed, so do nothing.
|
||||
pass
|
||||
quality_and_definition_changes_instance_containers.append(instance_container)
|
||||
if container_type == "quality_changes":
|
||||
quality_changes_instance_containers.append(instance_container)
|
||||
|
||||
if container_type == "definition_changes":
|
||||
definition_changes_extruder_count = instance_container.getProperty("machine_extruder_count", "value")
|
||||
|
@ -686,7 +722,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
stack.setName(global_stack_name_new)
|
||||
|
||||
container_stacks_added.append(stack)
|
||||
self._container_registry.addContainer(stack)
|
||||
# self._container_registry.addContainer(stack)
|
||||
containers_added.append(stack)
|
||||
else:
|
||||
Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
|
||||
|
@ -704,6 +740,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
return
|
||||
|
||||
# load extruder stack files
|
||||
has_extruder_stack_files = len(extruder_stack_files) > 0
|
||||
empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
|
||||
empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
|
||||
try:
|
||||
for extruder_stack_file in extruder_stack_files:
|
||||
container_id = self._stripFileToId(extruder_stack_file)
|
||||
|
@ -752,25 +791,33 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# If not extruder stacks were saved in the project file (pre 3.1) create one manually
|
||||
# We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this
|
||||
if not extruder_stacks:
|
||||
if self._resolve_strategies["machine"] == "new":
|
||||
stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder")
|
||||
# If we choose to override a machine but to create a new custom quality profile, the custom quality
|
||||
# profile is not immediately applied to the global_stack, so this fix for single extrusion machines
|
||||
# will use the current custom quality profile on the existing machine. The extra optional argument
|
||||
# in that function is used in this case to specify a new global stack quality_changes container so
|
||||
# the fix can correctly create and copy over the custom quality settings to the newly created extruder.
|
||||
new_global_quality_changes = None
|
||||
if self._resolve_strategies["quality_changes"] == "new" and len(quality_changes_instance_containers) > 0:
|
||||
new_global_quality_changes = quality_changes_instance_containers[0]
|
||||
|
||||
# Depending if the strategy is to create a new or override, the ids must be or not be unique
|
||||
stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder",
|
||||
new_global_quality_changes,
|
||||
create_new_ids = self._resolve_strategies["machine"] == "new")
|
||||
if new_global_quality_changes is not None:
|
||||
quality_changes_instance_containers.append(stack.qualityChanges)
|
||||
quality_and_definition_changes_instance_containers.append(stack.qualityChanges)
|
||||
if global_stack.quality.getId() in ("empty", "empty_quality"):
|
||||
stack.quality = empty_quality_container
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
# in case the extruder is newly created (for a single-extrusion machine), we need to override
|
||||
# the existing extruder stack.
|
||||
existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")]
|
||||
for idx in range(len(_ContainerIndexes.IndexTypeMap)):
|
||||
existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True)
|
||||
extruder_stacks.append(existing_extruder_stack)
|
||||
else:
|
||||
stack = global_stack.extruders.get("0")
|
||||
if not stack:
|
||||
# this should not happen
|
||||
Logger.log("e", "Cannot find any extruder in an existing global stack [%s].", global_stack.getId())
|
||||
if stack:
|
||||
if global_stack.quality.getId() in ("empty", "empty_quality"):
|
||||
stack.quality = empty_quality_container
|
||||
if self._resolve_strategies["machine"] == "override":
|
||||
# in case the extruder is newly created (for a single-extrusion machine), we need to override
|
||||
# the existing extruder stack.
|
||||
existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")]
|
||||
for idx in range(len(_ContainerIndexes.IndexTypeMap)):
|
||||
existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True)
|
||||
extruder_stacks.append(existing_extruder_stack)
|
||||
else:
|
||||
extruder_stacks.append(stack)
|
||||
extruder_stacks.append(stack)
|
||||
|
||||
except:
|
||||
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
|
||||
|
@ -779,6 +826,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
self._container_registry.removeContainer(container.getId())
|
||||
return
|
||||
|
||||
## In case there is a new machine and once the extruders are created, the global stack is added to the registry,
|
||||
# otherwise the accContainers function in CuraContainerRegistry will create an extruder stack and then creating
|
||||
# useless files
|
||||
if self._resolve_strategies["machine"] == "new":
|
||||
self._container_registry.addContainer(global_stack)
|
||||
|
||||
# Check quality profiles to make sure that if one stack has the "not supported" quality profile,
|
||||
# all others should have the same.
|
||||
|
@ -811,17 +863,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
quality_has_been_changed = False
|
||||
|
||||
if has_not_supported:
|
||||
empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
|
||||
for stack in [global_stack] + extruder_stacks_in_use:
|
||||
stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container)
|
||||
empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
|
||||
for stack in [global_stack] + extruder_stacks_in_use:
|
||||
stack.replaceContainer(_ContainerIndexes.QualityChanges, empty_quality_changes_container)
|
||||
quality_has_been_changed = True
|
||||
|
||||
else:
|
||||
empty_quality_changes_container = self._container_registry.findInstanceContainers(id="empty_quality_changes")[0]
|
||||
|
||||
# The machine in the project has non-empty quality and there are usable qualities for this machine.
|
||||
# We need to check if the current quality_type is still usable for this machine, if not, then the quality
|
||||
# will be reset to the "preferred quality" if present, otherwise "normal".
|
||||
|
@ -932,9 +979,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
# sanity checks
|
||||
# NOTE: The following cases SHOULD NOT happen!!!!
|
||||
if not old_container:
|
||||
if old_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"):
|
||||
Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
|
||||
changes_container_type, global_stack.getId())
|
||||
continue
|
||||
|
||||
# Replace the quality/definition changes container if it's in the GlobalStack
|
||||
# NOTE: we can get an empty container here, but the IDs will not match,
|
||||
|
@ -947,26 +995,29 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
continue
|
||||
|
||||
# Replace the quality/definition changes container if it's in one of the ExtruderStacks
|
||||
for each_extruder_stack in extruder_stacks:
|
||||
changes_container = None
|
||||
if changes_container_type == "quality_changes":
|
||||
changes_container = each_extruder_stack.qualityChanges
|
||||
elif changes_container_type == "definition_changes":
|
||||
changes_container = each_extruder_stack.definitionChanges
|
||||
|
||||
# sanity checks
|
||||
# NOTE: The following cases SHOULD NOT happen!!!!
|
||||
if not changes_container:
|
||||
Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
|
||||
changes_container_type, each_extruder_stack.getId())
|
||||
|
||||
# NOTE: we can get an empty container here, but the IDs will not match,
|
||||
# so this comparison is fine.
|
||||
if self._id_mapping.get(changes_container.getId()) == new_id:
|
||||
# Only apply the change if we have loaded extruder stacks from the project
|
||||
if has_extruder_stack_files:
|
||||
for each_extruder_stack in extruder_stacks:
|
||||
changes_container = None
|
||||
if changes_container_type == "quality_changes":
|
||||
each_extruder_stack.qualityChanges = each_changes_container
|
||||
changes_container = each_extruder_stack.qualityChanges
|
||||
elif changes_container_type == "definition_changes":
|
||||
each_extruder_stack.definitionChanges = each_changes_container
|
||||
changes_container = each_extruder_stack.definitionChanges
|
||||
|
||||
# sanity checks
|
||||
# NOTE: The following cases SHOULD NOT happen!!!!
|
||||
if changes_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"):
|
||||
Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
|
||||
changes_container_type, each_extruder_stack.getId())
|
||||
continue
|
||||
|
||||
# NOTE: we can get an empty container here, but the IDs will not match,
|
||||
# so this comparison is fine.
|
||||
if self._id_mapping.get(changes_container.getId()) == new_id:
|
||||
if changes_container_type == "quality_changes":
|
||||
each_extruder_stack.qualityChanges = each_changes_container
|
||||
elif changes_container_type == "definition_changes":
|
||||
each_extruder_stack.definitionChanges = each_changes_container
|
||||
|
||||
if self._resolve_strategies["material"] == "new":
|
||||
# the actual material instance container can have an ID such as
|
||||
|
@ -1002,8 +1053,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
stack.setNextStack(global_stack)
|
||||
stack.containersChanged.emit(stack.getTop())
|
||||
else:
|
||||
if quality_has_been_changed:
|
||||
CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
|
||||
CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
|
||||
|
||||
# Actually change the active machine.
|
||||
Application.getInstance().setGlobalContainerStack(global_stack)
|
||||
|
|
|
@ -97,7 +97,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|||
file_in_archive.compress_type = zipfile.ZIP_DEFLATED
|
||||
|
||||
# Do not include the network authentication keys
|
||||
ignore_keys = {"network_authentication_id", "network_authentication_key"}
|
||||
ignore_keys = {"network_authentication_id", "network_authentication_key", "octoprint_api_key"}
|
||||
serialized_data = container.serialize(ignored_metadata_keys = ignore_keys)
|
||||
|
||||
archive.writestr(file_in_archive, serialized_data)
|
||||
|
|
|
@ -427,11 +427,10 @@ class CuraEngineBackend(QObject, Backend):
|
|||
if not isinstance(source, SceneNode):
|
||||
return
|
||||
|
||||
# This case checks if the source node is a node that contains a GCode. In this case the
|
||||
# cached layer data is removed so the previous data is not rendered - CURA-4821
|
||||
# This case checks if the source node is a node that contains GCode. In this case the
|
||||
# current layer data is removed so the previous data is not rendered - CURA-4821
|
||||
if source.callDecoration("isBlockSlicing") and source.callDecoration("getLayerData"):
|
||||
if self._stored_optimized_layer_data:
|
||||
del self._stored_optimized_layer_data[source.callDecoration("getBuildPlateNumber")]
|
||||
self._stored_optimized_layer_data = {}
|
||||
|
||||
build_plate_changed = set()
|
||||
source_build_plate_number = source.callDecoration("getBuildPlateNumber")
|
||||
|
|
|
@ -15,7 +15,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|||
from UM.Settings.Validator import ValidatorState
|
||||
from UM.Settings.SettingRelation import RelationType
|
||||
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
|
@ -137,7 +137,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()):
|
||||
if type(node) is not SceneNode or not node.isSelectable():
|
||||
if type(node) is not CuraSceneNode or not node.isSelectable():
|
||||
continue
|
||||
|
||||
if self._checkStackForErrors(node.callDecoration("getStack")):
|
||||
|
@ -161,10 +161,15 @@ class StartSliceJob(Job):
|
|||
if getattr(node, "_outside_buildarea", False):
|
||||
continue
|
||||
|
||||
# Filter on current build plate
|
||||
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||
if build_plate_number is not None and build_plate_number != self._build_plate_number:
|
||||
continue
|
||||
|
||||
children = node.getAllChildren()
|
||||
children.append(node)
|
||||
for child_node in children:
|
||||
if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
|
||||
if type(child_node) is CuraSceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
|
||||
temp_list.append(child_node)
|
||||
|
||||
if temp_list:
|
||||
|
@ -176,7 +181,7 @@ class StartSliceJob(Job):
|
|||
temp_list = []
|
||||
has_printing_mesh = False
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if node.callDecoration("isSliceable") and type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
||||
if node.callDecoration("isSliceable") and type(node) is CuraSceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
||||
per_object_stack = node.callDecoration("getStack")
|
||||
is_non_printing_mesh = False
|
||||
if per_object_stack:
|
||||
|
|
|
@ -39,7 +39,7 @@ class CuraProfileReader(ProfileReader):
|
|||
|
||||
except zipfile.BadZipFile:
|
||||
# It must be an older profile from Cura 2.1.
|
||||
with open(file_name, encoding="utf-8") as fhandle:
|
||||
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)]
|
||||
|
||||
|
@ -52,10 +52,10 @@ class CuraProfileReader(ProfileReader):
|
|||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
if not "general" in parser:
|
||||
if "general" not in parser:
|
||||
Logger.log("w", "Missing required section 'general'.")
|
||||
return []
|
||||
if not "version" in parser["general"]:
|
||||
if "version" not in parser["general"]:
|
||||
Logger.log("w", "Missing required 'version' property")
|
||||
return []
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class GCodeReader(MeshReader):
|
|||
|
||||
# PreRead is used to get the correct flavor. If not, Marlin is set by default
|
||||
def preRead(self, file_name, *args, **kwargs):
|
||||
with open(file_name, "r") as file:
|
||||
with open(file_name, "r", encoding = "utf-8") as file:
|
||||
for line in file:
|
||||
if line[:len(self._flavor_keyword)] == self._flavor_keyword:
|
||||
try:
|
||||
|
|
|
@ -125,7 +125,10 @@ class LegacyProfileReader(ProfileReader):
|
|||
Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?")
|
||||
return None
|
||||
current_printer_definition = global_container_stack.definition
|
||||
profile.setDefinition(current_printer_definition.getId())
|
||||
quality_definition = current_printer_definition.getMetaDataEntry("quality_definition")
|
||||
if not quality_definition:
|
||||
quality_definition = current_printer_definition.getId()
|
||||
profile.setDefinition(quality_definition)
|
||||
for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations.
|
||||
old_setting_expression = dict_of_doom["translation"][new_setting]
|
||||
compiled = compile(old_setting_expression, new_setting, "eval")
|
||||
|
@ -162,20 +165,21 @@ class LegacyProfileReader(ProfileReader):
|
|||
data = stream.getvalue()
|
||||
profile.deserialize(data)
|
||||
|
||||
# The definition can get reset to fdmprinter during the deserialization's upgrade. Here we set the definition
|
||||
# again.
|
||||
profile.setDefinition(quality_definition)
|
||||
|
||||
#We need to return one extruder stack and one global stack.
|
||||
global_container_id = container_registry.uniqueName("Global Imported Legacy Profile")
|
||||
global_profile = profile.duplicate(new_id = global_container_id, new_name = profile_id) #Needs to have the same name as the extruder profile.
|
||||
global_profile.setDirty(True)
|
||||
|
||||
#Only the extruder stack has an extruder metadata entry.
|
||||
profile.addMetaDataEntry("extruder", ExtruderManager.getInstance().getActiveExtruderStack().definition.getId())
|
||||
profile_definition = "fdmprinter"
|
||||
from UM.Util import parseBool
|
||||
if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")):
|
||||
profile_definition = global_container_stack.getMetaDataEntry("quality_definition")
|
||||
if not profile_definition:
|
||||
profile_definition = global_container_stack.definition.getId()
|
||||
global_profile.setDefinition(profile_definition)
|
||||
|
||||
#Split all settings into per-extruder and global settings.
|
||||
for setting_key in profile.getAllKeys():
|
||||
settable_per_extruder = global_container_stack.getProperty(setting_key, "settable_per_extruder")
|
||||
if settable_per_extruder:
|
||||
global_profile.removeInstance(setting_key)
|
||||
else:
|
||||
profile.removeInstance(setting_key)
|
||||
|
||||
return [global_profile, profile]
|
||||
return [global_profile]
|
||||
|
|
|
@ -158,79 +158,4 @@ class MachineSettingsAction(MachineAction):
|
|||
@pyqtSlot(int)
|
||||
def updateMaterialForDiameter(self, extruder_position: int):
|
||||
# Updates the material container to a material that matches the material diameter set for the printer
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
if not self._global_container_stack.getMetaDataEntry("has_materials", False):
|
||||
return
|
||||
|
||||
extruder_stack = self._global_container_stack.extruders[str(extruder_position)]
|
||||
|
||||
material_diameter = extruder_stack.material.getProperty("material_diameter", "value")
|
||||
if not material_diameter:
|
||||
# in case of "empty" material
|
||||
material_diameter = 0
|
||||
|
||||
material_approximate_diameter = str(round(material_diameter))
|
||||
machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
|
||||
if not machine_diameter:
|
||||
if extruder_stack.definition.hasProperty("material_diameter", "value"):
|
||||
machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value")
|
||||
else:
|
||||
machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value")
|
||||
machine_approximate_diameter = str(round(machine_diameter))
|
||||
|
||||
if material_approximate_diameter != machine_approximate_diameter:
|
||||
Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
|
||||
|
||||
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
|
||||
materials_definition = self._global_container_stack.definition.getId()
|
||||
has_material_variants = self._global_container_stack.getMetaDataEntry("has_variants", False)
|
||||
else:
|
||||
materials_definition = "fdmprinter"
|
||||
has_material_variants = False
|
||||
|
||||
old_material = extruder_stack.material
|
||||
search_criteria = {
|
||||
"type": "material",
|
||||
"approximate_diameter": machine_approximate_diameter,
|
||||
"material": old_material.getMetaDataEntry("material", "value"),
|
||||
"brand": old_material.getMetaDataEntry("brand", "value"),
|
||||
"supplier": old_material.getMetaDataEntry("supplier", "value"),
|
||||
"color_name": old_material.getMetaDataEntry("color_name", "value"),
|
||||
"definition": materials_definition
|
||||
}
|
||||
if has_material_variants:
|
||||
search_criteria["variant"] = extruder_stack.variant.getId()
|
||||
|
||||
if old_material == self._empty_container:
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria.pop("definition", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Same material with new diameter is not found, search for generic version of the same material type
|
||||
search_criteria.pop("supplier", None)
|
||||
search_criteria.pop("brand", None)
|
||||
search_criteria["color_name"] = "Generic"
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Generic material with new diameter is not found, search for preferred material
|
||||
search_criteria.pop("color_name", None)
|
||||
search_criteria.pop("material", None)
|
||||
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Preferred material with new diameter is not found, search for any material
|
||||
search_criteria.pop("id", None)
|
||||
materials = self._container_registry.findInstanceContainers(**search_criteria)
|
||||
if not materials:
|
||||
# Just use empty material as a final fallback
|
||||
materials = [self._empty_container]
|
||||
|
||||
Logger.log("i", "Selecting new material: %s", materials[0].getId())
|
||||
|
||||
extruder_stack.material = materials[0]
|
||||
Application.getInstance().getExtruderManager().updateMaterialForDiameter(extruder_position)
|
||||
|
|
|
@ -25,7 +25,7 @@ class PluginBrowser(QObject, Extension):
|
|||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._api_version = 2
|
||||
self._api_version = 4
|
||||
self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version
|
||||
|
||||
self._plugin_list_request = None
|
||||
|
|
|
@ -98,11 +98,14 @@ class SimulationView(View):
|
|||
|
||||
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
|
||||
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
|
||||
self._compatibility_mode = True # for safety
|
||||
self._compatibility_mode = self._evaluateCompatibilityMode()
|
||||
|
||||
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
|
||||
title = catalog.i18nc("@info:title", "Simulation View"))
|
||||
|
||||
def _evaluateCompatibilityMode(self):
|
||||
return OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
|
||||
|
||||
def _resetSettings(self):
|
||||
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
|
||||
self._extruder_count = 0
|
||||
|
@ -127,7 +130,7 @@ class SimulationView(View):
|
|||
# Currently the RenderPass constructor requires a size > 0
|
||||
# This should be fixed in RenderPass's constructor.
|
||||
self._layer_pass = SimulationPass(1, 1)
|
||||
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
|
||||
self._compatibility_mode = self._evaluateCompatibilityMode()
|
||||
self._layer_pass.setSimulationView(self)
|
||||
return self._layer_pass
|
||||
|
||||
|
@ -534,8 +537,7 @@ class SimulationView(View):
|
|||
def _updateWithPreferences(self):
|
||||
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
|
||||
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
|
||||
self._compatibility_mode = OpenGLContext.isLegacyOpenGL() or bool(
|
||||
Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode"))
|
||||
self._compatibility_mode = self._evaluateCompatibilityMode()
|
||||
|
||||
self.setSimulationViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type"))));
|
||||
|
||||
|
|
|
@ -485,7 +485,7 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
// Gradient colors for layer thickness
|
||||
// Gradient colors for layer thickness (similar to parula colormap)
|
||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
||||
// Invert values because then the bar is rotated 90 degrees
|
||||
id: thicknessGradient
|
||||
|
@ -499,23 +499,23 @@ Item
|
|||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.000
|
||||
color: Qt.rgba(1, 0, 0, 1)
|
||||
color: Qt.rgba(1, 1, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.25
|
||||
color: Qt.rgba(0.5, 0.5, 0, 1)
|
||||
color: Qt.rgba(1, 0.75, 0.25, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.5
|
||||
color: Qt.rgba(0, 1, 0, 1)
|
||||
color: Qt.rgba(0, 0.75, 0.5, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.75
|
||||
color: Qt.rgba(0, 0.5, 0.5, 1)
|
||||
color: Qt.rgba(0, 0.375, 0.75, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.rgba(0, 0, 1, 1)
|
||||
color: Qt.rgba(0, 0, 0.5, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,9 +54,13 @@ vertex41core =
|
|||
vec4 layerThicknessGradientColor(float abs_value, float min_value, float max_value)
|
||||
{
|
||||
float value = (abs_value - min_value)/(max_value - min_value);
|
||||
float red = max(2*value-1, 0);
|
||||
float green = 1-abs(1-2*value);
|
||||
float blue = max(1-2*value, 0);
|
||||
float red = min(max(4*value-2, 0), 1);
|
||||
float green = min(1.5*value, 0.75);
|
||||
if (value > 0.75)
|
||||
{
|
||||
green = value;
|
||||
}
|
||||
float blue = 0.75-abs(0.25-value);
|
||||
return vec4(red, green, blue, 1.0);
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ class SliceInfo(Extension):
|
|||
"brand": extruder.material.getMetaData().get("brand", "")
|
||||
}
|
||||
extruder_position = int(extruder.getMetaDataEntry("position", "0"))
|
||||
if extruder_position in print_information.materialLengths:
|
||||
if len(print_information.materialLengths) > extruder_position:
|
||||
extruder_dict["material_used"] = print_information.materialLengths[extruder_position]
|
||||
extruder_dict["variant"] = extruder.variant.getName()
|
||||
extruder_dict["nozzle_size"] = extruder.getProperty("machine_nozzle_size", "value")
|
||||
|
|
71
plugins/SupportEraser/SupportEraser.py
Normal file
71
plugins/SupportEraser/SupportEraser.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Tool import Tool
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
import os
|
||||
import os.path
|
||||
|
||||
class SupportEraser(Tool):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._shortcut_key = Qt.Key_G
|
||||
self._controller = Application.getInstance().getController()
|
||||
|
||||
def event(self, event):
|
||||
super().event(event)
|
||||
|
||||
if event.type == Event.ToolActivateEvent:
|
||||
|
||||
# Load the remover mesh:
|
||||
self._createEraserMesh()
|
||||
|
||||
# After we load the mesh, deactivate the tool again:
|
||||
self.getController().setActiveTool(None)
|
||||
|
||||
def _createEraserMesh(self):
|
||||
# Selection.clear()
|
||||
|
||||
node = CuraSceneNode()
|
||||
|
||||
node.setName("Eraser")
|
||||
node.setSelectable(True)
|
||||
mesh = MeshBuilder()
|
||||
mesh.addCube(10,10,10)
|
||||
node.setMeshData(mesh.build())
|
||||
|
||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
||||
|
||||
node.addDecorator(SettingOverrideDecorator())
|
||||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
node.addDecorator(SliceableObjectDecorator())
|
||||
|
||||
stack = node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
|
||||
if not stack:
|
||||
node.addDecorator(SettingOverrideDecorator())
|
||||
stack = node.callDecoration("getStack")
|
||||
|
||||
print(stack)
|
||||
settings = stack.getTop()
|
||||
|
||||
if not (settings.getInstance("anti_overhang_mesh") and settings.getProperty("anti_overhang_mesh", "value")):
|
||||
definition = stack.getSettingDefinition("anti_overhang_mesh")
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
new_instance.setProperty("value", True)
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
|
||||
scene = self._controller.getScene()
|
||||
op = AddSceneNodeOperation(node, scene.getRoot())
|
||||
op.push()
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
20
plugins/SupportEraser/__init__.py
Normal file
20
plugins/SupportEraser/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import SupportEraser
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("uranium")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"tool": {
|
||||
"name": i18n_catalog.i18nc("@label", "Support Blocker"),
|
||||
"description": i18n_catalog.i18nc("@info:tooltip", "Create a volume in which supports are not printed."),
|
||||
"icon": "tool_icon.svg",
|
||||
"weight": 4
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return { "tool": SupportEraser.SupportEraser() }
|
8
plugins/SupportEraser/plugin.json
Normal file
8
plugins/SupportEraser/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Support Eraser",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
11
plugins/SupportEraser/tool_icon.svg
Normal file
11
plugins/SupportEraser/tool_icon.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<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 -->
|
||||
<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>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 866 B |
68
plugins/UCPWriter/UCPWriter.py
Normal file
68
plugins/UCPWriter/UCPWriter.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
import zipfile
|
||||
|
||||
from io import StringIO
|
||||
|
||||
from UM.Resources import Resources
|
||||
from UM.Mesh.MeshWriter import MeshWriter
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
MYPY = False
|
||||
try:
|
||||
if not MYPY:
|
||||
import xml.etree.cElementTree as ET
|
||||
except ImportError:
|
||||
Logger.log("w", "Unable to load cElementTree, switching to slower version")
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
class UCPWriter(MeshWriter):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._namespaces = {
|
||||
"content-types": "http://schemas.openxmlformats.org/package/2006/content-types",
|
||||
"relationships": "http://schemas.openxmlformats.org/package/2006/relationships",
|
||||
}
|
||||
|
||||
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
|
||||
self._archive = None # Reset archive
|
||||
archive = zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED)
|
||||
|
||||
gcode_file = zipfile.ZipInfo("3D/model.gcode")
|
||||
gcode_file.compress_type = zipfile.ZIP_DEFLATED
|
||||
|
||||
# Create content types file
|
||||
content_types_file = zipfile.ZipInfo("[Content_Types].xml")
|
||||
content_types_file.compress_type = zipfile.ZIP_DEFLATED
|
||||
content_types = ET.Element("Types", xmlns=self._namespaces["content-types"])
|
||||
|
||||
rels_type = ET.SubElement(content_types, "Default", Extension="rels",
|
||||
ContentType="application/vnd.openxmlformats-package.relationships+xml")
|
||||
gcode_type = ET.SubElement(content_types, "Default", Extension="gcode",
|
||||
ContentType="text/x-gcode")
|
||||
image_type = ET.SubElement(content_types, "Default", Extension="png",
|
||||
ContentType="image/png")
|
||||
|
||||
# Create _rels/.rels file
|
||||
relations_file = zipfile.ZipInfo("_rels/.rels")
|
||||
relations_file.compress_type = zipfile.ZIP_DEFLATED
|
||||
relations_element = ET.Element("Relationships", xmlns=self._namespaces["relationships"])
|
||||
|
||||
thumbnail_relation_element = ET.SubElement(relations_element, "Relationship", Target="/Metadata/thumbnail.png", Id="rel0",
|
||||
Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail")
|
||||
|
||||
model_relation_element = ET.SubElement(relations_element, "Relationship", Target="/3D/model.gcode",
|
||||
Id="rel1",
|
||||
Type="http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
||||
|
||||
gcode_string = StringIO()
|
||||
|
||||
PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_string, None)
|
||||
|
||||
archive.write(Resources.getPath(Resources.Images, "cura-icon.png"), "Metadata/thumbnail.png")
|
||||
|
||||
archive.writestr(gcode_file, gcode_string.getvalue())
|
||||
archive.writestr(content_types_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(content_types))
|
||||
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
|
||||
|
||||
archive.close()
|
25
plugins/UCPWriter/__init__.py
Normal file
25
plugins/UCPWriter/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import UCPWriter
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"mesh_writer": {
|
||||
"output": [
|
||||
{
|
||||
"mime_type": "application/x-ucp",
|
||||
"mode": UCPWriter.UCPWriter.OutputMode.BinaryMode,
|
||||
"extension": "UCP",
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "UCP File (WIP)")
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return { "mesh_writer": UCPWriter.UCPWriter() }
|
8
plugins/UCPWriter/plugin.json
Normal file
8
plugins/UCPWriter/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "UCP Writer",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides support for writing UCP files.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -287,7 +287,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._updatePrintJob(print_job, print_job_data)
|
||||
|
||||
if print_job.state != "queued": # Print job should be assigned to a printer.
|
||||
printer = self._getPrinterByKey(print_job_data["printer_uuid"])
|
||||
if print_job.state == "failed":
|
||||
# Print job was failed, so don't attach it to a printer.
|
||||
printer = None
|
||||
else:
|
||||
printer = self._getPrinterByKey(print_job_data["printer_uuid"])
|
||||
else: # The job can "reserve" a printer if some changes are required.
|
||||
printer = self._getPrinterByKey(print_job_data["assigned_to"])
|
||||
|
||||
|
|
|
@ -413,7 +413,7 @@ Rectangle
|
|||
{
|
||||
if(printJob.state == "printing" || printJob.state == "post_print")
|
||||
{
|
||||
return OutputDevice.getDateCompleted(printJob.time_total - printJob.time_elapsed)
|
||||
return OutputDevice.getDateCompleted(printJob.timeTotal - printJob.timeElapsed)
|
||||
}
|
||||
}
|
||||
return "";
|
||||
|
|
|
@ -126,7 +126,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
|||
def removeManualDevice(self, key, address = None):
|
||||
if key in self._discovered_devices:
|
||||
if not address:
|
||||
address = self._printers[key].ipAddress
|
||||
address = self._discovered_devices[key].ipAddress
|
||||
self._onRemoveDevice(key)
|
||||
|
||||
if address in self._manual_instances:
|
||||
|
|
|
@ -10,7 +10,7 @@ if MYPY:
|
|||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
|
||||
|
||||
class USBPrinterOuptutController(PrinterOutputController):
|
||||
class USBPrinterOutputController(PrinterOutputController):
|
||||
def __init__(self, output_device):
|
||||
super().__init__(output_device)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
|||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
|
||||
from .AutoDetectBaudJob import AutoDetectBaudJob
|
||||
from .USBPrinterOutputController import USBPrinterOuptutController
|
||||
from .USBPrinterOutputController import USBPrinterOutputController
|
||||
from .avr_isp import stk500v2, intelHex
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
||||
|
@ -237,7 +237,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
|
||||
# Ensure that a printer is created.
|
||||
self._printers = [PrinterOutputModel(output_controller=USBPrinterOuptutController(self), number_of_extruders=num_extruders)]
|
||||
self._printers = [PrinterOutputModel(output_controller=USBPrinterOutputController(self), number_of_extruders=num_extruders)]
|
||||
self._printers[0].updateName(container_stack.getName())
|
||||
self.setConnectionState(ConnectionState.connected)
|
||||
self._update_thread.start()
|
||||
|
@ -364,7 +364,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
elapsed_time = int(time() - self._print_start_time)
|
||||
print_job = self._printers[0].activePrintJob
|
||||
if print_job is None:
|
||||
print_job = PrintJobOutputModel(output_controller = USBPrinterOuptutController(self), name= Application.getInstance().getPrintInformation().jobName)
|
||||
print_job = PrintJobOutputModel(output_controller = USBPrinterOutputController(self), name= Application.getInstance().getPrintInformation().jobName)
|
||||
print_job.updateState("printing")
|
||||
self._printers[0].updateActivePrintJob(print_job)
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# Uranium is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from cura.MachineAction import MachineAction
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
||||
|
||||
|
@ -11,8 +10,6 @@ from UM.Application import Application
|
|||
from UM.Util import parseBool
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
import UM.Settings.InstanceContainer
|
||||
|
||||
|
||||
## The Ultimaker 2 can have a few revisions & upgrades.
|
||||
class UM2UpgradeSelection(MachineAction):
|
||||
|
@ -22,18 +19,29 @@ class UM2UpgradeSelection(MachineAction):
|
|||
|
||||
self._container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
self._current_global_stack = None
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
self.hasVariantsChanged.emit()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
if self._current_global_stack:
|
||||
self._current_global_stack.metaDataChanged.disconnect(self._onGlobalStackMetaDataChanged)
|
||||
|
||||
self._current_global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if self._current_global_stack:
|
||||
self._current_global_stack.metaDataChanged.connect(self._onGlobalStackMetaDataChanged)
|
||||
self._reset()
|
||||
|
||||
def _onGlobalStackMetaDataChanged(self):
|
||||
self._reset()
|
||||
|
||||
hasVariantsChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify = hasVariantsChanged)
|
||||
def hasVariants(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false"))
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def setHasVariants(self, has_variants = True):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
|
@ -62,3 +70,9 @@ class UM2UpgradeSelection(MachineAction):
|
|||
global_container_stack.extruders["0"].variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.emit()
|
||||
self._reset()
|
||||
|
||||
@pyqtProperty(bool, fset = setHasVariants, notify = hasVariantsChanged)
|
||||
def hasVariants(self):
|
||||
if self._current_global_stack:
|
||||
return parseBool(self._current_global_stack.getMetaDataEntry("has_variants", "false"))
|
||||
|
|
|
@ -13,6 +13,7 @@ import Cura 1.0 as Cura
|
|||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
|
||||
Item
|
||||
{
|
||||
id: upgradeSelectionMachineAction
|
||||
|
@ -39,12 +40,19 @@ Cura.MachineAction
|
|||
|
||||
CheckBox
|
||||
{
|
||||
id: olssonBlockCheckBox
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
text: catalog.i18nc("@label", "Olsson Block")
|
||||
checked: manager.hasVariants
|
||||
onClicked: manager.setHasVariants(checked)
|
||||
onClicked: manager.hasVariants = checked
|
||||
|
||||
Connections
|
||||
{
|
||||
target: manager
|
||||
onHasVariantsChanged: olssonBlockCheckBox.checked = manager.hasVariants
|
||||
}
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
|
|
@ -74,7 +74,7 @@ class VersionUpgrade22to24(VersionUpgrade):
|
|||
def __convertVariant(self, variant_path):
|
||||
# Copy the variant to the machine_instances/*_settings.inst.cfg
|
||||
variant_config = configparser.ConfigParser(interpolation=None)
|
||||
with open(variant_path, "r") as fhandle:
|
||||
with open(variant_path, "r", encoding = "utf-8") as fhandle:
|
||||
variant_config.read_file(fhandle)
|
||||
|
||||
config_name = "Unknown Variant"
|
||||
|
|
|
@ -59,6 +59,12 @@ _EMPTY_CONTAINER_DICT = {
|
|||
}
|
||||
|
||||
|
||||
# Renamed definition files
|
||||
_RENAMED_DEFINITION_DICT = {
|
||||
"jellybox": "imade3d_jellybox",
|
||||
}
|
||||
|
||||
|
||||
class VersionUpgrade30to31(VersionUpgrade):
|
||||
## Gets the version number from a CFG file in Uranium's 3.0 format.
|
||||
#
|
||||
|
@ -111,16 +117,9 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
if not parser.has_section(each_section):
|
||||
parser.add_section(each_section)
|
||||
|
||||
# Copy global quality changes to extruder quality changes for single extrusion machines
|
||||
if parser["metadata"]["type"] == "quality_changes":
|
||||
all_quality_changes = self._getSingleExtrusionMachineQualityChanges(parser)
|
||||
# Note that DO NOT!!! use the quality_changes returned from _getSingleExtrusionMachineQualityChanges().
|
||||
# Those are loaded from the hard drive which are original files that haven't been upgraded yet.
|
||||
# NOTE 2: The number can be 0 or 1 depends on whether you are loading it from the qualities folder or
|
||||
# from a project file. When you load from a project file, the custom profile may not be in cura
|
||||
# yet, so you will get 0.
|
||||
if len(all_quality_changes) <= 1 and not parser.has_option("metadata", "extruder"):
|
||||
self._createExtruderQualityChangesForSingleExtrusionMachine(filename, parser)
|
||||
# Check renamed definitions
|
||||
if "definition" in parser["general"] and parser["general"]["definition"] in _RENAMED_DEFINITION_DICT:
|
||||
parser["general"]["definition"] = _RENAMED_DEFINITION_DICT[parser["general"]["definition"]]
|
||||
|
||||
# Update version numbers
|
||||
parser["general"]["version"] = "2"
|
||||
|
@ -156,6 +155,10 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
if parser.has_option("containers", key) and parser["containers"][key] == "empty":
|
||||
parser["containers"][key] = specific_empty_container
|
||||
|
||||
# check renamed definition
|
||||
if parser.has_option("containers", "6") and parser["containers"]["6"] in _RENAMED_DEFINITION_DICT:
|
||||
parser["containers"]["6"] = _RENAMED_DEFINITION_DICT[parser["containers"]["6"]]
|
||||
|
||||
# Update version numbers
|
||||
if "general" not in parser:
|
||||
parser["general"] = {}
|
||||
|
@ -219,6 +222,10 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"]
|
||||
extruder_quality_changes_parser["general"]["definition"] = global_quality_changes["general"]["definition"]
|
||||
|
||||
# check renamed definition
|
||||
if extruder_quality_changes_parser["general"]["definition"] in _RENAMED_DEFINITION_DICT:
|
||||
extruder_quality_changes_parser["general"]["definition"] = _RENAMED_DEFINITION_DICT[extruder_quality_changes_parser["general"]["definition"]]
|
||||
|
||||
extruder_quality_changes_parser.add_section("metadata")
|
||||
extruder_quality_changes_parser["metadata"]["quality_type"] = global_quality_changes["metadata"]["quality_type"]
|
||||
extruder_quality_changes_parser["metadata"]["type"] = global_quality_changes["metadata"]["type"]
|
||||
|
@ -231,5 +238,5 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
|
||||
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
||||
|
||||
with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w") as f:
|
||||
with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w", encoding = "utf-8") as f:
|
||||
f.write(extruder_quality_changes_output.getvalue())
|
||||
|
|
|
@ -17,6 +17,8 @@ import UM.Dictionary
|
|||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
from .XmlMaterialValidator import XmlMaterialValidator
|
||||
|
||||
## Handles serializing and deserializing material containers from an XML file
|
||||
class XmlMaterialProfile(InstanceContainer):
|
||||
CurrentFdmMaterialVersion = "1.3"
|
||||
|
@ -480,6 +482,10 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if "adhesion_info" not in meta_data:
|
||||
meta_data["adhesion_info"] = ""
|
||||
|
||||
validation_message = XmlMaterialValidator.validateMaterialMetaData(meta_data)
|
||||
if validation_message is not None:
|
||||
raise Exception("Not valid material profile: %s" % (validation_message))
|
||||
|
||||
property_values = {}
|
||||
properties = data.iterfind("./um:properties/*", self.__namespaces)
|
||||
for entry in properties:
|
||||
|
@ -541,7 +547,6 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
Logger.log("w", "No definition found for machine ID %s", machine_id)
|
||||
continue
|
||||
|
||||
Logger.log("d", "Found definition for machine ID %s", machine_id)
|
||||
definition = definitions[0]
|
||||
|
||||
machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
|
||||
|
|
31
plugins/XmlMaterialProfile/XmlMaterialValidator.py
Normal file
31
plugins/XmlMaterialProfile/XmlMaterialValidator.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
|
||||
class XmlMaterialValidator():
|
||||
|
||||
@classmethod
|
||||
def validateMaterialMetaData(cls, validation_metadata):
|
||||
|
||||
if validation_metadata.get("GUID") is None:
|
||||
return "Missing GUID"
|
||||
|
||||
if validation_metadata.get("brand") is None:
|
||||
return "Missing Brand"
|
||||
|
||||
if validation_metadata.get("material") is None:
|
||||
return "Missing Material"
|
||||
|
||||
if validation_metadata.get("version") is None:
|
||||
return "Missing Version"
|
||||
|
||||
if validation_metadata.get("description") is None:
|
||||
return "Missing Description"
|
||||
|
||||
if validation_metadata.get("adhesion_info") is None:
|
||||
return "Missing Adhesion Info"
|
||||
|
||||
return None
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue