mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Merge branch 'main' into InsertAtLayerChange
This commit is contained in:
commit
acd8444465
643 changed files with 78742 additions and 71010 deletions
|
@ -17,6 +17,7 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
|
|||
from UM.Scene.GroupDecorator import GroupDecorator
|
||||
from UM.Scene.SceneNode import SceneNode # For typing.
|
||||
from UM.Scene.SceneNodeSettings import SceneNodeSettings
|
||||
from UM.Util import parseBool
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
|
@ -182,7 +183,7 @@ class ThreeMFReader(MeshReader):
|
|||
um_node.printOrder = int(setting_value)
|
||||
continue
|
||||
if key =="drop_to_buildplate":
|
||||
um_node.setSetting(SceneNodeSettings.AutoDropDown, eval(setting_value))
|
||||
um_node.setSetting(SceneNodeSettings.AutoDropDown, parseBool(setting_value))
|
||||
continue
|
||||
if key in known_setting_keys:
|
||||
setting_container.setProperty(key, "value", setting_value)
|
||||
|
|
|
@ -96,7 +96,8 @@ class ThreeMFWriter(MeshWriter):
|
|||
@staticmethod
|
||||
def _convertUMNodeToSavitarNode(um_node,
|
||||
transformation = Matrix(),
|
||||
exported_settings: Optional[Dict[str, Set[str]]] = None):
|
||||
exported_settings: Optional[Dict[str, Set[str]]] = None,
|
||||
center_mesh = False):
|
||||
"""Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
|
||||
|
||||
:returns: Uranium Scene node.
|
||||
|
@ -111,20 +112,26 @@ class ThreeMFWriter(MeshWriter):
|
|||
savitar_node = Savitar.SceneNode()
|
||||
savitar_node.setName(um_node.getName())
|
||||
|
||||
node_matrix = Matrix()
|
||||
mesh_data = um_node.getMeshData()
|
||||
# compensate for original center position, if object(s) is/are not around its zero position
|
||||
if mesh_data is not None:
|
||||
extents = mesh_data.getExtents()
|
||||
if extents is not None:
|
||||
# We use a different coordinate space while writing, so flip Z and Y
|
||||
center_vector = Vector(extents.center.x, extents.center.z, extents.center.y)
|
||||
node_matrix.setByTranslation(center_vector)
|
||||
node_matrix.multiply(um_node.getLocalTransformation())
|
||||
|
||||
matrix_string = ThreeMFWriter._convertMatrixToString(node_matrix.preMultiply(transformation))
|
||||
node_matrix = um_node.getLocalTransformation()
|
||||
node_matrix.preMultiply(transformation)
|
||||
|
||||
if center_mesh:
|
||||
center_matrix = Matrix()
|
||||
# compensate for original center position, if object(s) is/are not around its zero position
|
||||
if mesh_data is not None:
|
||||
extents = mesh_data.getExtents()
|
||||
if extents is not None:
|
||||
# We use a different coordinate space while writing, so flip Z and Y
|
||||
center_vector = Vector(-extents.center.x, -extents.center.y, -extents.center.z)
|
||||
center_matrix.setByTranslation(center_vector)
|
||||
node_matrix.preMultiply(center_matrix)
|
||||
|
||||
matrix_string = ThreeMFWriter._convertMatrixToString(node_matrix)
|
||||
|
||||
savitar_node.setTransformation(matrix_string)
|
||||
|
||||
if mesh_data is not None:
|
||||
savitar_node.getMeshData().setVerticesFromBytes(mesh_data.getVerticesAsByteArray())
|
||||
indices_array = mesh_data.getIndicesAsByteArray()
|
||||
|
@ -147,7 +154,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
for key in changed_setting_keys:
|
||||
savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value")))
|
||||
else:
|
||||
# We want to export only the specified settings
|
||||
# We want to export only the specified settings
|
||||
if um_node.getName() in exported_settings:
|
||||
model_exported_settings = exported_settings[um_node.getName()]
|
||||
|
||||
|
@ -283,7 +290,8 @@ class ThreeMFWriter(MeshWriter):
|
|||
for root_child in node.getChildren():
|
||||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child,
|
||||
transformation_matrix,
|
||||
exported_model_settings)
|
||||
exported_model_settings,
|
||||
center_mesh = True)
|
||||
if savitar_node:
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
else:
|
||||
|
@ -442,7 +450,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
def sceneNodesToString(scene_nodes: [SceneNode]) -> str:
|
||||
savitar_scene = Savitar.Scene()
|
||||
for scene_node in scene_nodes:
|
||||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node)
|
||||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True)
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
parser = Savitar.ThreeMFParser()
|
||||
scene_string = parser.sceneToString(savitar_scene)
|
||||
|
|
|
@ -544,7 +544,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
|
||||
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(),
|
||||
"Unable to slice because there are objects associated with disabled Extruder %s.") % job.getAssociatedDisabledExtruders(),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"),
|
||||
message_type = Message.MessageType.WARNING)
|
||||
self._error_message.show()
|
||||
|
|
|
@ -49,7 +49,20 @@ class StartJobResult(IntEnum):
|
|||
ObjectsWithDisabledExtruder = 8
|
||||
|
||||
|
||||
class GcodeStartEndFormatter(Formatter):
|
||||
class GcodeConditionState(IntEnum):
|
||||
OutsideCondition = 1
|
||||
ConditionFalse = 2
|
||||
ConditionTrue = 3
|
||||
ConditionDone = 4
|
||||
|
||||
|
||||
class GcodeInstruction(IntEnum):
|
||||
Skip = 1
|
||||
Evaluate = 2
|
||||
EvaluateAndWrite = 3
|
||||
|
||||
|
||||
class GcodeStartEndFormatter:
|
||||
# Formatter class that handles token expansion in start/end gcode
|
||||
# Example of a start/end gcode string:
|
||||
# ```
|
||||
|
@ -63,22 +76,50 @@ class GcodeStartEndFormatter(Formatter):
|
|||
# will be used. Alternatively, if the expression is formatted as "{[expression], [extruder_nr]}",
|
||||
# then the expression will be evaluated with the extruder stack of the specified extruder_nr.
|
||||
|
||||
_extruder_regex = re.compile(r"^\s*(?P<expression>.*)\s*,\s*(?P<extruder_nr_expr>.*)\s*$")
|
||||
_instruction_regex = re.compile(r"{(?P<condition>if|else|elif|endif)?\s*(?P<expression>.*?)\s*(?:,\s*(?P<extruder_nr_expr>.*))?\s*}(?P<end_of_line>\n?)")
|
||||
|
||||
def __init__(self, all_extruder_settings: Dict[str, Any], default_extruder_nr: int = -1) -> None:
|
||||
def __init__(self, all_extruder_settings: Dict[str, Dict[str, Any]], default_extruder_nr: int = -1) -> None:
|
||||
super().__init__()
|
||||
self._all_extruder_settings: Dict[str, Any] = all_extruder_settings
|
||||
self._all_extruder_settings: Dict[str, Dict[str, Any]] = all_extruder_settings
|
||||
self._default_extruder_nr: int = default_extruder_nr
|
||||
self._cura_application = CuraApplication.getInstance()
|
||||
self._extruder_manager = ExtruderManager.getInstance()
|
||||
|
||||
def get_field(self, field_name, args: [str], kwargs: dict) -> Tuple[str, str]:
|
||||
# get_field method parses all fields in the format-string and parses them individually to the get_value method.
|
||||
# e.g. for a string "Hello {foo.bar}" would the complete field "foo.bar" would be passed to get_field, and then
|
||||
# the individual parts "foo" and "bar" would be passed to get_value. This poses a problem for us, because want
|
||||
# to parse the entire field as a single expression. To solve this, we override the get_field method and return
|
||||
# the entire field as the expression.
|
||||
return self.get_value(field_name, args, kwargs), field_name
|
||||
def format(self, text: str) -> str:
|
||||
remaining_text: str = text
|
||||
result: str = ""
|
||||
|
||||
def get_value(self, expression: str, args: [str], kwargs: dict) -> str:
|
||||
self._condition_state: GcodeConditionState = GcodeConditionState.OutsideCondition
|
||||
|
||||
while len(remaining_text) > 0:
|
||||
next_code_match = self._instruction_regex.search(remaining_text)
|
||||
if next_code_match is not None:
|
||||
expression_start, expression_end = next_code_match.span()
|
||||
|
||||
if expression_start > 0:
|
||||
result += self._process_statement(remaining_text[:expression_start])
|
||||
|
||||
result += self._process_code(next_code_match)
|
||||
|
||||
remaining_text = remaining_text[expression_end:]
|
||||
|
||||
else:
|
||||
result += self._process_statement(remaining_text)
|
||||
remaining_text = ""
|
||||
|
||||
return result
|
||||
|
||||
def _process_statement(self, statement: str) -> str:
|
||||
if self._condition_state in [GcodeConditionState.OutsideCondition, GcodeConditionState.ConditionTrue]:
|
||||
return statement
|
||||
else:
|
||||
return ""
|
||||
|
||||
def _process_code(self, code: re.Match) -> str:
|
||||
condition: Optional[str] = code.group("condition")
|
||||
expression: Optional[str] = code.group("expression")
|
||||
extruder_nr_expr: Optional[str] = code.group("extruder_nr_expr")
|
||||
end_of_line: Optional[str] = code.group("end_of_line")
|
||||
|
||||
# The following variables are not settings, but only become available after slicing.
|
||||
# when these variables are encountered, we return them as-is. They are replaced later
|
||||
|
@ -87,53 +128,100 @@ class GcodeStartEndFormatter(Formatter):
|
|||
if expression in post_slice_data_variables:
|
||||
return f"{{{expression}}}"
|
||||
|
||||
extruder_nr = str(self._default_extruder_nr)
|
||||
extruder_nr: str = str(self._default_extruder_nr)
|
||||
instruction: GcodeInstruction = GcodeInstruction.Skip
|
||||
|
||||
# The settings may specify a specific extruder to use. This is done by
|
||||
# formatting the expression as "{expression}, {extruder_nr_expr}". If the
|
||||
# expression is formatted like this, we extract the extruder_nr and use
|
||||
# it to get the value from the correct extruder stack.
|
||||
match = self._extruder_regex.match(expression)
|
||||
if match:
|
||||
expression = match.group("expression")
|
||||
extruder_nr_expr = match.group("extruder_nr_expr")
|
||||
|
||||
if extruder_nr_expr.isdigit():
|
||||
extruder_nr = extruder_nr_expr
|
||||
if condition is None:
|
||||
# This is a classic statement
|
||||
if self._condition_state in [GcodeConditionState.OutsideCondition, GcodeConditionState.ConditionTrue]:
|
||||
# Skip and move to next
|
||||
instruction = GcodeInstruction.EvaluateAndWrite
|
||||
else:
|
||||
# This is a condition statement, first check validity
|
||||
if condition == "if":
|
||||
if self._condition_state != GcodeConditionState.OutsideCondition:
|
||||
raise SyntaxError("Nested conditions are not supported")
|
||||
else:
|
||||
# We get the value of the extruder_nr_expr from `_all_extruder_settings` dictionary
|
||||
# rather than the global container stack. The `_all_extruder_settings["-1"]` is a
|
||||
# dict-representation of the global container stack, with additional properties such
|
||||
# as `initial_extruder_nr`. As users may enter such expressions we can't use the
|
||||
# global container stack.
|
||||
extruder_nr = str(self._all_extruder_settings["-1"].get(extruder_nr_expr, "-1"))
|
||||
if self._condition_state == GcodeConditionState.OutsideCondition:
|
||||
raise SyntaxError("Condition should start with an 'if' statement")
|
||||
|
||||
if extruder_nr in self._all_extruder_settings:
|
||||
additional_variables = self._all_extruder_settings[extruder_nr].copy()
|
||||
else:
|
||||
Logger.warning(f"Extruder {extruder_nr} does not exist, using global settings")
|
||||
additional_variables = self._all_extruder_settings["-1"].copy()
|
||||
if condition == "if":
|
||||
# First instruction, just evaluate it
|
||||
instruction = GcodeInstruction.Evaluate
|
||||
|
||||
# Add the arguments and keyword arguments to the additional settings. These
|
||||
# are currently _not_ used, but they are added for consistency with the
|
||||
# base Formatter class.
|
||||
for key, value in enumerate(args):
|
||||
additional_variables[key] = value
|
||||
for key, value in kwargs.items():
|
||||
additional_variables[key] = value
|
||||
else:
|
||||
if self._condition_state == GcodeConditionState.ConditionTrue:
|
||||
# We have reached the next condition after a valid one has been found, skip the rest
|
||||
self._condition_state = GcodeConditionState.ConditionDone
|
||||
|
||||
if extruder_nr == "-1":
|
||||
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
else:
|
||||
container_stack = ExtruderManager.getInstance().getExtruderStack(extruder_nr)
|
||||
if not container_stack:
|
||||
if condition == "elif":
|
||||
if self._condition_state == GcodeConditionState.ConditionFalse:
|
||||
# New instruction, and valid condition has not been reached so far => evaluate it
|
||||
instruction = GcodeInstruction.Evaluate
|
||||
else:
|
||||
# New instruction, but valid condition has already been reached => skip it
|
||||
instruction = GcodeInstruction.Skip
|
||||
|
||||
elif condition == "else":
|
||||
instruction = GcodeInstruction.Skip # Never evaluate, expression should be empty
|
||||
if self._condition_state == GcodeConditionState.ConditionFalse:
|
||||
# Fallback instruction, and valid condition has not been reached so far => active next
|
||||
self._condition_state = GcodeConditionState.ConditionTrue
|
||||
|
||||
elif condition == "endif":
|
||||
instruction = GcodeInstruction.Skip # Never evaluate, expression should be empty
|
||||
self._condition_state = GcodeConditionState.OutsideCondition
|
||||
|
||||
if instruction >= GcodeInstruction.Evaluate and extruder_nr_expr is not None:
|
||||
extruder_nr_function = SettingFunction(extruder_nr_expr)
|
||||
container_stack = self._cura_application.getGlobalContainerStack()
|
||||
|
||||
# We add the variables contained in `_all_extruder_settings["-1"]`, which is a dict-representation of the
|
||||
# global container stack, with additional properties such as `initial_extruder_nr`. As users may enter such
|
||||
# expressions we can't use the global container stack. The variables contained in the global container stack
|
||||
# will then be inserted twice, which is not optimal but works well.
|
||||
extruder_nr = str(extruder_nr_function(container_stack, additional_variables=self._all_extruder_settings["-1"]))
|
||||
|
||||
if instruction >= GcodeInstruction.Evaluate:
|
||||
if extruder_nr in self._all_extruder_settings:
|
||||
additional_variables = self._all_extruder_settings[extruder_nr].copy()
|
||||
else:
|
||||
Logger.warning(f"Extruder {extruder_nr} does not exist, using global settings")
|
||||
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
additional_variables = self._all_extruder_settings["-1"].copy()
|
||||
|
||||
setting_function = SettingFunction(expression)
|
||||
value = setting_function(container_stack, additional_variables=additional_variables)
|
||||
if extruder_nr == "-1":
|
||||
container_stack = self._cura_application.getGlobalContainerStack()
|
||||
else:
|
||||
container_stack = self._extruder_manager.getExtruderStack(extruder_nr)
|
||||
if not container_stack:
|
||||
Logger.warning(f"Extruder {extruder_nr} does not exist, using global settings")
|
||||
container_stack = self._cura_application.getGlobalContainerStack()
|
||||
|
||||
return value
|
||||
setting_function = SettingFunction(expression)
|
||||
value = setting_function(container_stack, additional_variables=additional_variables)
|
||||
|
||||
if instruction == GcodeInstruction.Evaluate:
|
||||
if value:
|
||||
self._condition_state = GcodeConditionState.ConditionTrue
|
||||
else:
|
||||
self._condition_state = GcodeConditionState.ConditionFalse
|
||||
|
||||
return ""
|
||||
else:
|
||||
value_str = str(value)
|
||||
|
||||
if end_of_line is not None:
|
||||
# If we are evaluating an expression that is not a condition, restore the end of line
|
||||
value_str += end_of_line
|
||||
|
||||
return value_str
|
||||
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
class StartSliceJob(Job):
|
||||
|
@ -146,6 +234,7 @@ class StartSliceJob(Job):
|
|||
self._slice_message: Arcus.PythonMessage = slice_message
|
||||
self._is_cancelled: bool = False
|
||||
self._build_plate_number: Optional[int] = None
|
||||
self._associated_disabled_extruders: Optional[str] = None
|
||||
|
||||
# cache for all setting values from all stacks (global & extruder) for the current machine
|
||||
self._all_extruders_settings: Optional[Dict[str, Any]] = None
|
||||
|
@ -153,6 +242,9 @@ class StartSliceJob(Job):
|
|||
def getSliceMessage(self) -> Arcus.PythonMessage:
|
||||
return self._slice_message
|
||||
|
||||
def getAssociatedDisabledExtruders(self) -> Optional[str]:
|
||||
return self._associated_disabled_extruders
|
||||
|
||||
def setBuildPlate(self, build_plate_number: int) -> None:
|
||||
self._build_plate_number = build_plate_number
|
||||
|
||||
|
@ -334,7 +426,7 @@ class StartSliceJob(Job):
|
|||
if has_model_with_disabled_extruders:
|
||||
self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
|
||||
associated_disabled_extruders = {p + 1 for p in associated_disabled_extruders}
|
||||
self.setMessage(", ".join(map(str, sorted(associated_disabled_extruders))))
|
||||
self._associated_disabled_extruders = ", ".join(map(str, sorted(associated_disabled_extruders)))
|
||||
return
|
||||
|
||||
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
|
||||
|
@ -362,7 +454,12 @@ class StartSliceJob(Job):
|
|||
for extruder_stack in global_stack.extruderList:
|
||||
self._buildExtruderMessage(extruder_stack)
|
||||
|
||||
for plugin in CuraApplication.getInstance().getBackendPlugins():
|
||||
backend_plugins = CuraApplication.getInstance().getBackendPlugins()
|
||||
|
||||
# Sort backend plugins by name. Not a very good strategy, but at least it is repeatable. This will be improved later.
|
||||
backend_plugins = sorted(backend_plugins, key=lambda backend_plugin: backend_plugin.getId())
|
||||
|
||||
for plugin in backend_plugins:
|
||||
if not plugin.usePlugin():
|
||||
continue
|
||||
for slot in plugin.getSupportedSlots():
|
||||
|
@ -461,6 +558,9 @@ class StartSliceJob(Job):
|
|||
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
|
||||
result["initial_extruder_nr"] = CuraApplication.getInstance().getExtruderManager().getInitialExtruderNr()
|
||||
|
||||
# If adding or changing a setting here, please update the associated wiki page
|
||||
# https://github.com/Ultimaker/Cura/wiki/Start-End-G%E2%80%90Code
|
||||
|
||||
return result
|
||||
|
||||
def _cacheAllExtruderSettings(self):
|
||||
|
@ -550,12 +650,16 @@ class StartSliceJob(Job):
|
|||
start_gcode = settings["machine_start_gcode"]
|
||||
# Remove all the comments from the start g-code
|
||||
start_gcode = re.sub(r";.+?(\n|$)", "\n", start_gcode)
|
||||
bed_temperature_settings = ["material_bed_temperature", "material_bed_temperature_layer_0"]
|
||||
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr}
|
||||
settings["material_bed_temp_prepend"] = re.search(pattern, start_gcode) == None
|
||||
print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature", "print_temperature"]
|
||||
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr}
|
||||
settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) is None
|
||||
|
||||
if settings["material_bed_temp_prepend"]:
|
||||
bed_temperature_settings = ["material_bed_temperature", "material_bed_temperature_layer_0"]
|
||||
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr}
|
||||
settings["material_bed_temp_prepend"] = re.search(pattern, start_gcode) == None
|
||||
|
||||
if settings["material_print_temp_prepend"]:
|
||||
print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature", "print_temperature"]
|
||||
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr}
|
||||
settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) is None
|
||||
|
||||
# Replace the setting tokens in start and end g-code.
|
||||
# Use values from the first used extruder by default so we get the expected temperatures
|
||||
|
|
|
@ -208,7 +208,14 @@ Item
|
|||
anchors.rightMargin: UM.Theme.getSize("thin_margin").height
|
||||
|
||||
enabled: UM.Backend.state == UM.Backend.Done
|
||||
currentIndex: UM.Backend.state == UM.Backend.Done ? dfFilenameTextfield.text.startsWith("MM")? 1 : 0 : 2
|
||||
|
||||
// Pre-select the correct index, depending on the situation (see the model-property below):
|
||||
// - Don't select any post-slice-file-format when the engine isn't done.
|
||||
// - Choose either the S-series or the Makerbot-series of printers' format otherwise, depending on the active printer.
|
||||
// This way, the user can just click 'save' without having to worry about wether or not the format is right.
|
||||
property int isMakerbotFormat: Cura.MachineManager.activeMachine.getOutputFileFormats.includes("application/x-makerbot") || Cura.MachineManager.activeMachine.getOutputFileFormats.includes("application/x-makerbot-sketch")
|
||||
property int isBackendDone: UM.Backend.state == UM.Backend.Done
|
||||
currentIndex: isBackendDone ? (isMakerbotFormat ? 1 : 0) : 2
|
||||
|
||||
textRole: "text"
|
||||
valueRole: "value"
|
||||
|
|
|
@ -182,7 +182,7 @@ Item
|
|||
Cura.GcodeTextArea // "Extruder Start G-code"
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: buttonLearnMore.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
width: base.columnWidth - UM.Theme.getSize("default_margin").width
|
||||
|
@ -196,7 +196,7 @@ Item
|
|||
Cura.GcodeTextArea // "Extruder End G-code"
|
||||
{
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottom: buttonLearnMore.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.right: parent.right
|
||||
width: base.columnWidth - UM.Theme.getSize("default_margin").width
|
||||
|
@ -206,5 +206,17 @@ Item
|
|||
settingKey: "machine_extruder_end_code"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
}
|
||||
|
||||
Cura.TertiaryButton
|
||||
{
|
||||
id: buttonLearnMore
|
||||
|
||||
text: catalog.i18nc("@button", "Learn more")
|
||||
iconSource: UM.Theme.getIcon("LinkExternal")
|
||||
isIconOnRightSide: true
|
||||
onClicked: Qt.openUrlExternally("https://github.com/Ultimaker/Cura/wiki/Start-End-G%E2%80%90Code")
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -376,7 +376,7 @@ Item
|
|||
anchors
|
||||
{
|
||||
top: upperBlock.bottom
|
||||
bottom: parent.bottom
|
||||
bottom: buttonLearnMore.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
|
@ -403,5 +403,19 @@ Item
|
|||
settingKey: "machine_end_gcode"
|
||||
settingStoreIndex: propertyStoreIndex
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Cura.TertiaryButton
|
||||
{
|
||||
id: buttonLearnMore
|
||||
|
||||
text: catalog.i18nc("@button", "Learn more")
|
||||
iconSource: UM.Theme.getIcon("LinkExternal")
|
||||
isIconOnRightSide: true
|
||||
onClicked: Qt.openUrlExternally("https://github.com/Ultimaker/Cura/wiki/Start-End-G%E2%80%90Code")
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# Copyright (c) 2023 UltiMaker
|
||||
# Copyright (c) 2024 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from io import StringIO, BufferedIOBase
|
||||
import json
|
||||
from typing import cast, List, Optional, Dict
|
||||
from typing import cast, List, Optional, Dict, Tuple
|
||||
from zipfile import BadZipFile, ZipFile, ZIP_DEFLATED
|
||||
import pyDulcificum as du
|
||||
|
||||
|
@ -19,6 +18,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.FormatMaps import FormatMaps
|
||||
from cura.Snapshot import Snapshot
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
from cura.CuraVersion import ConanInstalls
|
||||
|
@ -39,16 +39,27 @@ class MakerbotWriter(MeshWriter):
|
|||
suffixes=["makerbot"]
|
||||
)
|
||||
)
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name="application/x-makerbot-sketch",
|
||||
comment="Makerbot Toolpath Package",
|
||||
suffixes=["makerbot"]
|
||||
)
|
||||
)
|
||||
|
||||
_PNG_FORMATS = [
|
||||
_PNG_FORMAT = [
|
||||
{"prefix": "isometric_thumbnail", "width": 120, "height": 120},
|
||||
{"prefix": "isometric_thumbnail", "width": 320, "height": 320},
|
||||
{"prefix": "isometric_thumbnail", "width": 640, "height": 640},
|
||||
{"prefix": "thumbnail", "width": 90, "height": 90},
|
||||
]
|
||||
|
||||
_PNG_FORMAT_METHOD = [
|
||||
{"prefix": "thumbnail", "width": 140, "height": 106},
|
||||
{"prefix": "thumbnail", "width": 212, "height": 300},
|
||||
{"prefix": "thumbnail", "width": 960, "height": 1460},
|
||||
{"prefix": "thumbnail", "width": 90, "height": 90},
|
||||
]
|
||||
|
||||
_META_VERSION = "3.0.0"
|
||||
|
||||
# must be called from the main thread because of OpenGL
|
||||
|
@ -74,6 +85,7 @@ class MakerbotWriter(MeshWriter):
|
|||
return None
|
||||
|
||||
def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode=MeshWriter.OutputMode.BinaryMode) -> bool:
|
||||
metadata, file_format = self._getMeta(nodes)
|
||||
if mode != MeshWriter.OutputMode.BinaryMode:
|
||||
Logger.log("e", "MakerbotWriter does not support text mode.")
|
||||
self.setInformation(catalog.i18nc("@error:not supported", "MakerbotWriter does not support text mode."))
|
||||
|
@ -92,14 +104,20 @@ class MakerbotWriter(MeshWriter):
|
|||
|
||||
gcode_text_io = StringIO()
|
||||
success = gcode_writer.write(gcode_text_io, None)
|
||||
|
||||
filename, filedata = "", ""
|
||||
# Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||
if not success:
|
||||
self.setInformation(gcode_writer.getInformation())
|
||||
return False
|
||||
|
||||
json_toolpaths = du.gcode_2_miracle_jtp(gcode_text_io.getvalue())
|
||||
metadata = self._getMeta(nodes)
|
||||
match file_format:
|
||||
case "application/x-makerbot-sketch":
|
||||
filename, filedata = "print.gcode", gcode_text_io.getvalue()
|
||||
self._PNG_FORMATS = self._PNG_FORMAT
|
||||
case "application/x-makerbot":
|
||||
filename, filedata = "print.jsontoolpath", du.gcode_2_miracle_jtp(gcode_text_io.getvalue())
|
||||
self._PNG_FORMATS = self._PNG_FORMAT + self._PNG_FORMAT_METHOD
|
||||
case _:
|
||||
raise Exception("Unsupported Mime type")
|
||||
|
||||
png_files = []
|
||||
for png_format in self._PNG_FORMATS:
|
||||
|
@ -116,10 +134,34 @@ class MakerbotWriter(MeshWriter):
|
|||
try:
|
||||
with ZipFile(stream, "w", compression=ZIP_DEFLATED) as zip_stream:
|
||||
zip_stream.writestr("meta.json", json.dumps(metadata, indent=4))
|
||||
zip_stream.writestr("print.jsontoolpath", json_toolpaths)
|
||||
zip_stream.writestr(filename, filedata)
|
||||
for png_file in png_files:
|
||||
file, data = png_file["file"], png_file["data"]
|
||||
zip_stream.writestr(file, data)
|
||||
api = CuraApplication.getInstance().getCuraAPI()
|
||||
metadata_json = api.interface.settings.getSliceMetadata()
|
||||
|
||||
# All the mapping stuff we have to do:
|
||||
product_to_id_map = FormatMaps.getProductIdMap()
|
||||
printer_name_map = FormatMaps.getInversePrinterNameMap()
|
||||
extruder_type_map = FormatMaps.getInverseExtruderTypeMap()
|
||||
material_map = FormatMaps.getInverseMaterialMap()
|
||||
for key, value in metadata_json.items():
|
||||
if "all_settings" in value:
|
||||
if "machine_name" in value["all_settings"]:
|
||||
machine_name = value["all_settings"]["machine_name"]
|
||||
if machine_name in product_to_id_map:
|
||||
machine_name = product_to_id_map[machine_name][0]
|
||||
value["all_settings"]["machine_name"] = printer_name_map.get(machine_name, machine_name)
|
||||
if "machine_nozzle_id" in value["all_settings"]:
|
||||
extruder_type = value["all_settings"]["machine_nozzle_id"]
|
||||
value["all_settings"]["machine_nozzle_id"] = extruder_type_map.get(extruder_type, extruder_type)
|
||||
if "material_type" in value["all_settings"]:
|
||||
material_type = value["all_settings"]["material_type"]
|
||||
value["all_settings"]["material_type"] = material_map.get(material_type, material_type)
|
||||
|
||||
slice_metadata = json.dumps(metadata_json, separators=(", ", ": "), indent=4)
|
||||
zip_stream.writestr("slicemetadata.json", slice_metadata)
|
||||
except (IOError, OSError, BadZipFile) as ex:
|
||||
Logger.log("e", f"Could not write to (.makerbot) file because: '{ex}'.")
|
||||
self.setInformation(catalog.i18nc("@error", "MakerbotWriter could not save to the designated path."))
|
||||
|
@ -127,7 +169,7 @@ class MakerbotWriter(MeshWriter):
|
|||
|
||||
return True
|
||||
|
||||
def _getMeta(self, root_nodes: List[SceneNode]) -> Dict[str, any]:
|
||||
def _getMeta(self, root_nodes: List[SceneNode]) -> Tuple[Dict[str, any], str]:
|
||||
application = CuraApplication.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
@ -143,7 +185,9 @@ class MakerbotWriter(MeshWriter):
|
|||
nodes.append(node)
|
||||
|
||||
meta = dict()
|
||||
|
||||
# This is a bit of a "hack", the mime type should be passed through with the export writer but
|
||||
# since this is not the case we get the mime type from the global stack instead
|
||||
file_format = global_stack.definition.getMetaDataEntry("file_formats")
|
||||
meta["bot_type"] = global_stack.definition.getMetaDataEntry("reference_machine_id")
|
||||
|
||||
bounds: Optional[AxisAlignedBox] = None
|
||||
|
@ -155,7 +199,8 @@ class MakerbotWriter(MeshWriter):
|
|||
bounds = node_bounds
|
||||
else:
|
||||
bounds = bounds + node_bounds
|
||||
|
||||
if file_format == "application/x-makerbot-sketch":
|
||||
bounds = None
|
||||
if bounds is not None:
|
||||
meta["bounding_box"] = {
|
||||
"x_min": bounds.left,
|
||||
|
@ -196,7 +241,7 @@ class MakerbotWriter(MeshWriter):
|
|||
meta["extruder_temperature"] = materials_temps[0]
|
||||
meta["extruder_temperatures"] = materials_temps
|
||||
|
||||
meta["model_counts"] = [{"count": 1, "name": node.getName()} for node in nodes]
|
||||
meta["model_counts"] = [{"count": len(nodes), "name": "instance0"}]
|
||||
|
||||
tool_types = [extruder.variant.getMetaDataEntry("reference_extruder_id") for extruder in extruders]
|
||||
meta["tool_type"] = tool_types[0]
|
||||
|
@ -205,14 +250,13 @@ class MakerbotWriter(MeshWriter):
|
|||
meta["version"] = MakerbotWriter._META_VERSION
|
||||
|
||||
meta["preferences"] = dict()
|
||||
for node in nodes:
|
||||
bounds = node.getBoundingBox()
|
||||
meta["preferences"][str(node.getName())] = {
|
||||
"machineBounds": [bounds.right, bounds.back, bounds.left, bounds.front] if bounds is not None else None,
|
||||
"printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory,
|
||||
}
|
||||
bounds = application.getBuildVolume().getBoundingBox()
|
||||
meta["preferences"]["instance0"] = {
|
||||
"machineBounds": [bounds.right, bounds.back, bounds.left, bounds.front] if bounds is not None else None,
|
||||
"printMode": CuraApplication.getInstance().getIntentManager().currentIntentCategory,
|
||||
}
|
||||
|
||||
meta["miracle_config"] = {"gaggles": {str(node.getName()): {} for node in nodes}}
|
||||
meta["miracle_config"] = {"gaggles": {"instance0": {}}}
|
||||
|
||||
version_info = dict()
|
||||
cura_engine_info = ConanInstalls.get("curaengine", {"version": "unknown", "revision": "unknown"})
|
||||
|
@ -245,7 +289,7 @@ class MakerbotWriter(MeshWriter):
|
|||
# platform_temperature
|
||||
# total_commands
|
||||
|
||||
return meta
|
||||
return meta, file_format
|
||||
|
||||
|
||||
def meterToMillimeter(value: float) -> float:
|
||||
|
|
|
@ -11,14 +11,23 @@ catalog = i18nCatalog("cura")
|
|||
def getMetaData():
|
||||
file_extension = "makerbot"
|
||||
return {
|
||||
"mesh_writer": {
|
||||
"output": [{
|
||||
"extension": file_extension,
|
||||
"description": catalog.i18nc("@item:inlistbox", "Makerbot Printfile"),
|
||||
"mime_type": "application/x-makerbot",
|
||||
"mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode,
|
||||
}],
|
||||
}
|
||||
"mesh_writer":
|
||||
{
|
||||
"output": [
|
||||
{
|
||||
"extension": file_extension,
|
||||
"description": catalog.i18nc("@item:inlistbox", "Makerbot Printfile"),
|
||||
"mime_type": "application/x-makerbot",
|
||||
"mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode,
|
||||
},
|
||||
{
|
||||
"extension": file_extension,
|
||||
"description": catalog.i18nc("@item:inlistbox", "Makerbot Sketch Printfile"),
|
||||
"mime_type": "application/x-makerbot-sketch",
|
||||
"mode": MakerbotWriter.MakerbotWriter.OutputMode.BinaryMode,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ from UM.Settings.InstanceContainer import InstanceContainer
|
|||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
from cura.API import CuraAPI
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
|
@ -85,7 +86,8 @@ class UFPWriter(MeshWriter):
|
|||
try:
|
||||
archive.addContentType(extension="json", mime_type="application/json")
|
||||
setting_textio = StringIO()
|
||||
json.dump(self._getSliceMetadata(), setting_textio, separators=(", ", ": "), indent=4)
|
||||
api = CuraApplication.getInstance().getCuraAPI()
|
||||
json.dump(api.interface.settings.getSliceMetadata(), setting_textio, separators=(", ", ": "), indent=4)
|
||||
steam = archive.getStream(SLICE_METADATA_PATH)
|
||||
steam.write(setting_textio.getvalue().encode("UTF-8"))
|
||||
except EnvironmentError as e:
|
||||
|
@ -210,57 +212,3 @@ class UFPWriter(MeshWriter):
|
|||
return [{"name": item.getName()}
|
||||
for item in DepthFirstIterator(node)
|
||||
if item.getMeshData() is not None and not item.callDecoration("isNonPrintingMesh")]
|
||||
|
||||
def _getSliceMetadata(self) -> Dict[str, Dict[str, Dict[str, str]]]:
|
||||
"""Get all changed settings and all settings. For each extruder and the global stack"""
|
||||
print_information = CuraApplication.getInstance().getPrintInformation()
|
||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
settings = {
|
||||
"material": {
|
||||
"length": print_information.materialLengths,
|
||||
"weight": print_information.materialWeights,
|
||||
"cost": print_information.materialCosts,
|
||||
},
|
||||
"global": {
|
||||
"changes": {},
|
||||
"all_settings": {},
|
||||
},
|
||||
"quality": asdict(machine_manager.activeQualityDisplayNameMap()),
|
||||
}
|
||||
|
||||
def _retrieveValue(container: InstanceContainer, setting_: str):
|
||||
value_ = container.getProperty(setting_, "value")
|
||||
for _ in range(0, 1024): # Prevent possibly endless loop by not using a limit.
|
||||
if not isinstance(value_, SettingFunction):
|
||||
return value_ # Success!
|
||||
value_ = value_(container)
|
||||
return 0 # Fallback value after breaking possibly endless loop.
|
||||
|
||||
global_stack = cast(GlobalStack, Application.getInstance().getGlobalContainerStack())
|
||||
|
||||
# Add global user or quality changes
|
||||
global_flattened_changes = InstanceContainer.createMergedInstanceContainer(global_stack.userChanges, global_stack.qualityChanges)
|
||||
for setting in global_flattened_changes.getAllKeys():
|
||||
settings["global"]["changes"][setting] = _retrieveValue(global_flattened_changes, setting)
|
||||
|
||||
# Get global all settings values without user or quality changes
|
||||
for setting in global_stack.getAllKeys():
|
||||
settings["global"]["all_settings"][setting] = _retrieveValue(global_stack, setting)
|
||||
|
||||
for i, extruder in enumerate(global_stack.extruderList):
|
||||
# Add extruder fields to settings dictionary
|
||||
settings[f"extruder_{i}"] = {
|
||||
"changes": {},
|
||||
"all_settings": {},
|
||||
}
|
||||
|
||||
# Add extruder user or quality changes
|
||||
extruder_flattened_changes = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder.qualityChanges)
|
||||
for setting in extruder_flattened_changes.getAllKeys():
|
||||
settings[f"extruder_{i}"]["changes"][setting] = _retrieveValue(extruder_flattened_changes, setting)
|
||||
|
||||
# Get extruder all settings values without user or quality changes
|
||||
for setting in extruder.getAllKeys():
|
||||
settings[f"extruder_{i}"]["all_settings"][setting] = _retrieveValue(extruder, setting)
|
||||
|
||||
return settings
|
||||
|
|
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Sketch.png
Normal file
BIN
plugins/UM3NetworkPrinting/resources/png/MakerBot Sketch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 185 KiB |
|
@ -42,7 +42,7 @@ class CloudApiClient:
|
|||
CLUSTER_API_ROOT = f"{ROOT_PATH}/connect/v1"
|
||||
CURA_API_ROOT = f"{ROOT_PATH}/cura/v1"
|
||||
|
||||
DEFAULT_REQUEST_TIMEOUT = 10 # seconds
|
||||
DEFAULT_REQUEST_TIMEOUT = 30 # seconds
|
||||
|
||||
# In order to avoid garbage collection we keep the callbacks in this list.
|
||||
_anti_gc_callbacks = [] # type: List[Callable[[Any], None]]
|
||||
|
|
|
@ -331,7 +331,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
return False
|
||||
|
||||
[printer, *_] = self._printers
|
||||
return printer.type in ("MakerBot Method X", "MakerBot Method XL")
|
||||
return printer.type in ("MakerBot Method X", "MakerBot Method XL", "MakerBot Sketch")
|
||||
|
||||
@pyqtProperty(bool, notify=_cloudClusterPrintersChanged)
|
||||
def supportsPrintJobActions(self) -> bool:
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
"ultimaker_method": "MakerBot Method",
|
||||
"ultimaker_methodx": "MakerBot Method X",
|
||||
"ultimaker_methodxl": "MakerBot Method XL",
|
||||
"ultimaker_factor4": "Ultimaker Factor 4"
|
||||
"ultimaker_factor4": "Ultimaker Factor 4",
|
||||
"ultimaker_sketch": "MakerBot Sketch"
|
||||
}
|
||||
|
|
|
@ -152,9 +152,3 @@ class VersionUpgrade22to24(VersionUpgrade):
|
|||
config.write(output)
|
||||
return [filename], [output.getvalue()]
|
||||
|
||||
def getCfgVersion(self, serialised: str) -> int:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
|
|
@ -33,23 +33,6 @@ _renamed_i18n = {
|
|||
|
||||
|
||||
class VersionUpgrade27to30(VersionUpgrade):
|
||||
## Gets the version number from a CFG file in Uranium's 2.7 format.
|
||||
#
|
||||
# Since the format may change, this is implemented for the 2.7 format only
|
||||
# and needs to be included in the version upgrade system rather than
|
||||
# globally in Uranium.
|
||||
#
|
||||
# \param serialised The serialised form of a CFG file.
|
||||
# \return The version number stored in the CFG file.
|
||||
# \raises ValueError The format of the version number in the file is
|
||||
# incorrect.
|
||||
# \raises KeyError The format of the file is incorrect.
|
||||
def getCfgVersion(self, serialised: str) -> int:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
||||
## Upgrades a preferences file from version 2.7 to 3.0.
|
||||
#
|
||||
|
|
|
@ -33,12 +33,6 @@ default_qualities_per_nozzle_and_material = {
|
|||
|
||||
|
||||
class VersionUpgrade460to462(VersionUpgrade):
|
||||
def getCfgVersion(self, serialised: str) -> int:
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
format_version = int(parser.get("general", "version")) # Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||
setting_version = int(parser.get("metadata", "setting_version", fallback = "0"))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
||||
def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
|
||||
"""
|
||||
|
|
|
@ -11,12 +11,14 @@ import xml.etree.ElementTree as ET
|
|||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from UM.Logger import Logger
|
||||
from UM.Decorators import CachedMemberFunctions
|
||||
import UM.Dictionary
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.FormatMaps import FormatMaps
|
||||
from cura.Machines.VariantType import VariantType
|
||||
|
||||
try:
|
||||
|
@ -70,6 +72,8 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
Logger.log("w", "Can't change metadata {key} of material {material_id} because it's read-only.".format(key = key, material_id = self.getId()))
|
||||
return
|
||||
|
||||
CachedMemberFunctions.clearInstanceCache(self)
|
||||
|
||||
# Some metadata such as diameter should also be instantiated to be a setting. Go though all values for the
|
||||
# "properties" field and apply the new values to SettingInstances as well.
|
||||
new_setting_values_dict = {}
|
||||
|
@ -249,7 +253,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
machine_variant_map[definition_id][variant_name] = variant_dict
|
||||
|
||||
# Map machine human-readable names to IDs
|
||||
product_id_map = self.getProductIdMap()
|
||||
product_id_map = FormatMaps.getProductIdMap()
|
||||
|
||||
for definition_id, container in machine_container_map.items():
|
||||
definition_id = container.getMetaDataEntry("definition")
|
||||
|
@ -479,6 +483,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
first.append(element)
|
||||
|
||||
def clearData(self):
|
||||
CachedMemberFunctions.clearInstanceCache(self)
|
||||
self._metadata = {
|
||||
"id": self.getId(),
|
||||
"name": ""
|
||||
|
@ -518,6 +523,8 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
def deserialize(self, serialized, file_name = None):
|
||||
"""Overridden from InstanceContainer"""
|
||||
|
||||
CachedMemberFunctions.clearInstanceCache(self)
|
||||
|
||||
containers_to_add = []
|
||||
# update the serialized data first
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
|
@ -647,7 +654,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
self._dirty = False
|
||||
|
||||
# Map machine human-readable names to IDs
|
||||
product_id_map = self.getProductIdMap()
|
||||
product_id_map = FormatMaps.getProductIdMap()
|
||||
|
||||
machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
|
||||
for machine in machines:
|
||||
|
@ -923,7 +930,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
result_metadata.append(base_metadata)
|
||||
|
||||
# Map machine human-readable names to IDs
|
||||
product_id_map = cls.getProductIdMap()
|
||||
product_id_map = FormatMaps.getProductIdMap()
|
||||
|
||||
for machine in data.iterfind("./um:settings/um:machine", cls.__namespaces):
|
||||
machine_compatibility = common_compatibility
|
||||
|
@ -1083,10 +1090,8 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
# Skip material properties (eg diameter) or metadata (eg GUID)
|
||||
return
|
||||
|
||||
if instance.value is True:
|
||||
data = "yes"
|
||||
elif instance.value is False:
|
||||
data = "no"
|
||||
if tag_name != "cura:setting" and isinstance(instance.value, bool):
|
||||
data = "yes" if instance.value else "no"
|
||||
else:
|
||||
data = str(instance.value)
|
||||
|
||||
|
@ -1129,29 +1134,6 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
id_list = list(id_list)
|
||||
return id_list
|
||||
|
||||
__product_to_id_map: Optional[Dict[str, List[str]]] = None
|
||||
|
||||
@classmethod
|
||||
def getProductIdMap(cls) -> Dict[str, List[str]]:
|
||||
"""Gets a mapping from product names in the XML files to their definition IDs.
|
||||
|
||||
This loads the mapping from a file.
|
||||
"""
|
||||
if cls.__product_to_id_map is not None:
|
||||
return cls.__product_to_id_map
|
||||
|
||||
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath("XmlMaterialProfile"))
|
||||
product_to_id_file = os.path.join(plugin_path, "product_to_id.json")
|
||||
with open(product_to_id_file, encoding = "utf-8") as f:
|
||||
contents = ""
|
||||
for line in f:
|
||||
contents += line if "#" not in line else "".join([line.replace("#", str(n)) for n in range(1, 12)])
|
||||
cls.__product_to_id_map = json.loads(contents)
|
||||
cls.__product_to_id_map = {key: [value] for key, value in cls.__product_to_id_map.items()}
|
||||
#This also loads "Ultimaker S5" -> "ultimaker_s5" even though that is not strictly necessary with the default to change spaces into underscores.
|
||||
#However it is not always loaded with that default; this mapping is also used in serialize() without that default.
|
||||
return cls.__product_to_id_map
|
||||
|
||||
@staticmethod
|
||||
def _parseCompatibleValue(value: str):
|
||||
"""Parse the value of the "material compatible" property."""
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"Ultimaker #": "ultimaker#",
|
||||
"Ultimaker # Extended": "ultimaker#_extended",
|
||||
"Ultimaker # Extended+": "ultimaker#_extended_plus",
|
||||
"Ultimaker # Go": "ultimaker#_go",
|
||||
"Ultimaker #+": "ultimaker#_plus",
|
||||
"Ultimaker #+ Connect": "ultimaker#_plus_connect",
|
||||
"Ultimaker S#": "ultimaker_s#",
|
||||
"Ultimaker Factor #": "ultimaker_factor#",
|
||||
"Ultimaker Original": "ultimaker_original",
|
||||
"Ultimaker Original+": "ultimaker_original_plus",
|
||||
"Ultimaker Original Dual Extrusion": "ultimaker_original_dual",
|
||||
"IMADE3D JellyBOX": "imade3d_jellybox",
|
||||
"DUAL600": "strateo3d",
|
||||
"IDEX420": "strateo3d_IDEX420",
|
||||
"IDEX420 Duplicate": "strateo3d_IDEX420_duplicate",
|
||||
"IDEX420 Mirror": "strateo3d_IDEX420_mirror",
|
||||
"UltiMaker Method": "ultimaker_method",
|
||||
"UltiMaker Method X": "ultimaker_methodx",
|
||||
"UltiMaker Method XL": "ultimaker_methodxl",
|
||||
"UltiMaker Sketch": "ultimaker_sketch",
|
||||
"UltiMaker Sketch Large": "ultimaker_sketch_large"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue