1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Mesh.MeshWriter import MeshWriter
from UM.Logger import Logger
from UM.Application import Application
from UM.Settings.InstanceContainer import InstanceContainer
from cura.Settings.ExtruderManager import ExtruderManager
import re #For escaping characters in the settings.
import json
import copy
## Writes g-code to a file.
#
# While this poses as a mesh writer, what this really does is take the g-code
# in the entire scene and write it to an output device. Since the g-code of a
# single mesh isn't separable from the rest what with rafts and travel moves
# and all, it doesn't make sense to write just a single mesh.
#
# So this plug-in takes the g-code that is stored in the root of the scene
# node tree, adds a bit of extra information about the profiles and writes
# that to the output device.
class GCodeWriter(MeshWriter):
## The file format version of the serialised g-code.
#
# It can only read settings with the same version as the version it was
# written with. If the file format is changed in a way that breaks reverse
# compatibility, increment this version number!
version = 3
## Dictionary that defines how characters are escaped when embedded in
# g-code.
#
# Note that the keys of this dictionary are regex strings. The values are
# not.
escape_characters = {
re.escape("\\"): "\\\\", # The escape character.
re.escape("\n"): "\\n", # Newlines. They break off the comment.
re.escape("\r"): "\\r" # Carriage return. Windows users may need this for visualisation in their editors.
}
def __init__(self):
super().__init__()
## Writes the g-code for the entire scene to a stream.
#
# Note that even though the function accepts a collection of nodes, the
# entire scene is always written to the file since it is not possible to
# separate the g-code for just specific nodes.
#
# \param stream The stream to write the g-code to.
# \param nodes This is ignored.
# \param mode Additional information on how to format the g-code in the
# file. This must always be text mode.
def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode):
if mode != MeshWriter.OutputMode.TextMode:
Logger.log("e", "GCode Writer does not support non-text mode.")
return False
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene()
gcode_list = getattr(scene, "gcode_list")[active_build_plate]
if gcode_list:
for gcode in gcode_list:
stream.write(gcode)
# Serialise the current container stack and put it at the end of the file.
settings = self._serialiseSettings(Application.getInstance().getGlobalContainerStack())
stream.write(settings)
return True
return False
## Create a new container with container 2 as base and container 1 written over it.
def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
flat_container = InstanceContainer(instance_container2.getName())
# The metadata includes id, name and definition
flat_container.setMetaData(copy.deepcopy(instance_container2.getMetaData()))
if instance_container1.getDefinition():
flat_container.setDefinition(instance_container1.getDefinition().getId())
for key in instance_container2.getAllKeys():
flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value"))
for key in instance_container1.getAllKeys():
flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value"))
return flat_container
## Serialises a container stack to prepare it for writing at the end of the
# g-code.
#
# The settings are serialised, and special characters (including newline)
# are escaped.
#
# \param settings A container stack to serialise.
# \return A serialised string of the settings.
def _serialiseSettings(self, stack):
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix)
container_with_profile = stack.qualityChanges
if not container_with_profile:
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
return ""
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
# If the quality changes is not set, we need to set type manually
if flat_global_container.getMetaDataEntry("type", None) is None:
flat_global_container.addMetaDataEntry("type", "quality_changes")
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_global_container.getMetaDataEntry("quality_type", None) is None:
flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
serialized = flat_global_container.serialize()
data = {"global_quality": serialized}
for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")):
extruder_quality = extruder.qualityChanges
if not extruder_quality:
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
continue
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
# If the quality changes is not set, we need to set type manually
if flat_extruder_quality.getMetaDataEntry("type", None) is None:
flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
# Ensure that extruder is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId())
# Ensure that quality_type is set. (Can happen if we have empty quality changes).
if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized)
json_string = json.dumps(data)
# Escape characters that have a special meaning in g-code comments.
pattern = re.compile("|".join(GCodeWriter.escape_characters.keys()))
# Perform the replacement with a regular expression.
escaped_string = pattern.sub(lambda m: GCodeWriter.escape_characters[re.escape(m.group(0))], json_string)
# Introduce line breaks so that each comment is no longer than 80 characters. Prepend each line with the prefix.
result = ""
# Lines have 80 characters, so the payload of each line is 80 - prefix.
for pos in range(0, len(escaped_string), 80 - prefix_length):
result += prefix + escaped_string[pos : pos + 80 - prefix_length] + "\n"
return result
|