|
@@ -1,4 +1,4 @@
|
|
|
-# Copyright (c) 2016 Aleph Objects, Inc.
|
|
|
+# Copyright (c) 2017 Aleph Objects, Inc.
|
|
|
# Cura is released under the terms of the LGPLv3 or higher.
|
|
|
|
|
|
from UM.Application import Application
|
|
@@ -40,7 +40,8 @@ class GCodeReader(MeshReader):
|
|
|
self._extruder_number = 0
|
|
|
self._clearValues()
|
|
|
self._scene_node = None
|
|
|
- self._position = namedtuple('Position', ['x', 'y', 'z', 'e'])
|
|
|
+ # X, Y, Z position, F feedrate and E extruder values are stored
|
|
|
+ self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e'])
|
|
|
self._is_layers_in_file = False # Does the Gcode have the layers comment?
|
|
|
self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
|
|
|
self._current_layer_thickness = 0.2 # default
|
|
@@ -48,7 +49,9 @@ class GCodeReader(MeshReader):
|
|
|
Preferences.getInstance().addPreference("gcodereader/show_caution", True)
|
|
|
|
|
|
def _clearValues(self):
|
|
|
+ self._filament_diameter = 2.85
|
|
|
self._extruder_number = 0
|
|
|
+ self._extrusion_length_offset = [0]
|
|
|
self._layer_type = LayerPolygon.Inset0Type
|
|
|
self._layer_number = 0
|
|
|
self._previous_z = 0
|
|
@@ -97,7 +100,7 @@ class GCodeReader(MeshReader):
|
|
|
def _createPolygon(self, layer_thickness, path, extruder_offsets):
|
|
|
countvalid = 0
|
|
|
for point in path:
|
|
|
- if point[3] > 0:
|
|
|
+ if point[5] > 0:
|
|
|
countvalid += 1
|
|
|
if countvalid >= 2:
|
|
|
# we know what to do now, no need to count further
|
|
@@ -115,27 +118,56 @@ class GCodeReader(MeshReader):
|
|
|
line_types = numpy.empty((count - 1, 1), numpy.int32)
|
|
|
line_widths = numpy.empty((count - 1, 1), numpy.float32)
|
|
|
line_thicknesses = numpy.empty((count - 1, 1), numpy.float32)
|
|
|
- # TODO: need to calculate actual line width based on E values
|
|
|
+ line_feedrates = numpy.empty((count - 1, 1), numpy.float32)
|
|
|
line_widths[:, 0] = 0.35 # Just a guess
|
|
|
line_thicknesses[:, 0] = layer_thickness
|
|
|
points = numpy.empty((count, 3), numpy.float32)
|
|
|
+ extrusion_values = numpy.empty((count, 1), numpy.float32)
|
|
|
i = 0
|
|
|
for point in path:
|
|
|
points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]]
|
|
|
+ extrusion_values[i] = point[4]
|
|
|
if i > 0:
|
|
|
- line_types[i - 1] = point[3]
|
|
|
- if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
|
|
|
+ line_feedrates[i - 1] = point[3]
|
|
|
+ line_types[i - 1] = point[5]
|
|
|
+ if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
|
|
|
line_widths[i - 1] = 0.1
|
|
|
+ line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines
|
|
|
+ else:
|
|
|
+ line_widths[i - 1] = self._calculateLineWidth(points[i], points[i-1], extrusion_values[i], extrusion_values[i-1], layer_thickness)
|
|
|
i += 1
|
|
|
|
|
|
- this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses)
|
|
|
+ this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses, line_feedrates)
|
|
|
this_poly.buildCache()
|
|
|
|
|
|
this_layer.polygons.append(this_poly)
|
|
|
return True
|
|
|
|
|
|
+ def _calculateLineWidth(self, current_point, previous_point, current_extrusion, previous_extrusion, layer_thickness):
|
|
|
+ # Area of the filament
|
|
|
+ Af = (self._filament_diameter / 2) ** 2 * numpy.pi
|
|
|
+ # Length of the extruded filament
|
|
|
+ de = current_extrusion - previous_extrusion
|
|
|
+ # Volumne of the extruded filament
|
|
|
+ dVe = de * Af
|
|
|
+ # Length of the printed line
|
|
|
+ dX = numpy.sqrt((current_point[0] - previous_point[0])**2 + (current_point[2] - previous_point[2])**2)
|
|
|
+ # When the extruder recovers from a retraction, we get zero distance
|
|
|
+ if dX == 0:
|
|
|
+ return 0.1
|
|
|
+ # Area of the printed line. This area is a rectangle
|
|
|
+ Ae = dVe / dX
|
|
|
+ # This area is a rectangle with area equal to layer_thickness * layer_width
|
|
|
+ line_width = Ae / layer_thickness
|
|
|
+
|
|
|
+ # A threshold is set to avoid weird paths in the GCode
|
|
|
+ if line_width > 1.2:
|
|
|
+ return 0.35
|
|
|
+ return line_width
|
|
|
+
|
|
|
def _gCode0(self, position, params, path):
|
|
|
- x, y, z, e = position
|
|
|
+ x, y, z, f, e = position
|
|
|
+
|
|
|
if self._is_absolute_positioning:
|
|
|
x = params.x if params.x is not None else x
|
|
|
y = params.y if params.y is not None else y
|
|
@@ -145,22 +177,24 @@ class GCodeReader(MeshReader):
|
|
|
y += params.y if params.y is not None else 0
|
|
|
z += params.z if params.z is not None else 0
|
|
|
|
|
|
+ f = params.f if params.f is not None else f
|
|
|
+
|
|
|
if params.e is not None:
|
|
|
new_extrusion_value = params.e if self._is_absolute_positioning else e[self._extruder_number] + params.e
|
|
|
if new_extrusion_value > e[self._extruder_number]:
|
|
|
- path.append([x, y, z, self._layer_type]) # extrusion
|
|
|
+ path.append([x, y, z, f, params.e + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
|
|
|
else:
|
|
|
- path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction
|
|
|
+ path.append([x, y, z, f, params.e + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
|
|
|
e[self._extruder_number] = new_extrusion_value
|
|
|
|
|
|
# Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions
|
|
|
# Also, 1.5 is a heuristic for any priming or whatsoever, we skip those.
|
|
|
if z > self._previous_z and (z - self._previous_z < 1.5):
|
|
|
- self._current_layer_thickness = z - self._previous_z + 0.05 # allow a tiny overlap
|
|
|
+ self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
|
|
|
self._previous_z = z
|
|
|
else:
|
|
|
- path.append([x, y, z, LayerPolygon.MoveCombingType])
|
|
|
- return self._position(x, y, z, e)
|
|
|
+ path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
|
|
|
+ return self._position(x, y, z, f, e)
|
|
|
|
|
|
# G0 and G1 should be handled exactly the same.
|
|
|
_gCode1 = _gCode0
|
|
@@ -171,6 +205,7 @@ class GCodeReader(MeshReader):
|
|
|
params.x if params.x is not None else position.x,
|
|
|
params.y if params.y is not None else position.y,
|
|
|
0,
|
|
|
+ position.f,
|
|
|
position.e)
|
|
|
|
|
|
## Set the absolute positioning
|
|
@@ -187,11 +222,14 @@ class GCodeReader(MeshReader):
|
|
|
# For example: G92 X10 will set the X to 10 without any physical motion.
|
|
|
def _gCode92(self, position, params, path):
|
|
|
if params.e is not None:
|
|
|
+ # Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
|
|
|
+ self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
|
|
|
position.e[self._extruder_number] = params.e
|
|
|
return self._position(
|
|
|
params.x if params.x is not None else position.x,
|
|
|
params.y if params.y is not None else position.y,
|
|
|
params.z if params.z is not None else position.z,
|
|
|
+ params.f if params.f is not None else position.f,
|
|
|
position.e)
|
|
|
|
|
|
def _processGCode(self, G, line, position, path):
|
|
@@ -199,7 +237,7 @@ class GCodeReader(MeshReader):
|
|
|
line = line.split(";", 1)[0] # Remove comments (if any)
|
|
|
if func is not None:
|
|
|
s = line.upper().split(" ")
|
|
|
- x, y, z, e = None, None, None, None
|
|
|
+ x, y, z, f, e = None, None, None, None, None
|
|
|
for item in s[1:]:
|
|
|
if len(item) <= 1:
|
|
|
continue
|
|
@@ -211,17 +249,20 @@ class GCodeReader(MeshReader):
|
|
|
y = float(item[1:])
|
|
|
if item[0] == "Z":
|
|
|
z = float(item[1:])
|
|
|
+ if item[0] == "F":
|
|
|
+ f = float(item[1:]) / 60
|
|
|
if item[0] == "E":
|
|
|
e = float(item[1:])
|
|
|
if self._is_absolute_positioning and ((x is not None and x < 0) or (y is not None and y < 0)):
|
|
|
self._center_is_zero = True
|
|
|
- params = self._position(x, y, z, e)
|
|
|
+ params = self._position(x, y, z, f, e)
|
|
|
return func(position, params, path)
|
|
|
return position
|
|
|
|
|
|
def _processTCode(self, T, line, position, path):
|
|
|
self._extruder_number = T
|
|
|
if self._extruder_number + 1 > len(position.e):
|
|
|
+ self._extrusion_length_offset.extend([0] * (self._extruder_number - len(position.e) + 1))
|
|
|
position.e.extend([0] * (self._extruder_number - len(position.e) + 1))
|
|
|
return position
|
|
|
|
|
@@ -240,6 +281,8 @@ class GCodeReader(MeshReader):
|
|
|
def read(self, file_name):
|
|
|
Logger.log("d", "Preparing to load %s" % file_name)
|
|
|
self._cancelled = False
|
|
|
+ # We obtain the filament diameter from the selected printer to calculate line widths
|
|
|
+ self._filament_diameter = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value")
|
|
|
|
|
|
scene_node = SceneNode()
|
|
|
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
|
|
@@ -277,7 +320,7 @@ class GCodeReader(MeshReader):
|
|
|
|
|
|
Logger.log("d", "Parsing %s..." % file_name)
|
|
|
|
|
|
- current_position = self._position(0, 0, 0, [0])
|
|
|
+ current_position = self._position(0, 0, 0, 0, [0])
|
|
|
current_path = []
|
|
|
|
|
|
for line in file:
|
|
@@ -310,6 +353,7 @@ class GCodeReader(MeshReader):
|
|
|
else:
|
|
|
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
|
|
|
|
|
|
+ # When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder
|
|
|
if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
|
|
|
try:
|
|
|
layer_number = int(line[len(self._layer_keyword):])
|
|
@@ -325,17 +369,12 @@ class GCodeReader(MeshReader):
|
|
|
|
|
|
G = self._getInt(line, "G")
|
|
|
if G is not None:
|
|
|
+ # When find a movement, the new posistion is calculated and added to the current_path, but
|
|
|
+ # don't need to create a polygon until the end of the layer
|
|
|
current_position = self._processGCode(G, line, current_position, current_path)
|
|
|
-
|
|
|
- # < 2 is a heuristic for a movement only, that should not be counted as a layer
|
|
|
- if current_position.z > last_z and abs(current_position.z - last_z) < 2:
|
|
|
- if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
|
|
|
- current_path.clear()
|
|
|
- if not self._is_layers_in_file:
|
|
|
- self._layer_number += 1
|
|
|
-
|
|
|
continue
|
|
|
|
|
|
+ # When changing the extruder, the polygon with the stored paths is computed
|
|
|
if line.startswith("T"):
|
|
|
T = self._getInt(line, "T")
|
|
|
if T is not None:
|
|
@@ -344,8 +383,8 @@ class GCodeReader(MeshReader):
|
|
|
|
|
|
current_position = self._processTCode(T, line, current_position, current_path)
|
|
|
|
|
|
- # "Flush" leftovers
|
|
|
- if not self._is_layers_in_file and len(current_path) > 1:
|
|
|
+ # "Flush" leftovers. Last layer paths are still stored
|
|
|
+ if len(current_path) > 1:
|
|
|
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
|
|
|
self._layer_number += 1
|
|
|
current_path.clear()
|