Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/OctoPrint/OctoPrint.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKestin Goforth <kgoforth1503@gmail.com>2022-09-28 15:36:48 +0300
committerGitHub <noreply@github.com>2022-09-28 15:36:48 +0300
commit4c86459c1bb2cd72152ffad37bf5ad05b9159276 (patch)
treeffe282582f3acc98629d0aeaf445550625c2987d
parentffdda85c39ae1cfad2211accb40ca44317fc9d45 (diff)
✨ Analyze non-extruding moves and notify if exceeds print volume (#4579)
* Track travel moves and notify if exceeds print volume * Implement changes from pr review * Update GcodeAnalysisQueue docstring for travel area * Update docs for GCODE analysis information * Update exceedance messages for translation
-rw-r--r--docs/api/datamodel.rst44
-rw-r--r--src/octoprint/cli/analysis.py14
-rw-r--r--src/octoprint/filemanager/analysis.py42
-rw-r--r--src/octoprint/static/js/app/viewmodels/files.js114
-rw-r--r--src/octoprint/util/gcodeInterpreter.py82
5 files changed, 229 insertions, 67 deletions
diff --git a/docs/api/datamodel.rst b/docs/api/datamodel.rst
index 595c91d0d..0b5033def 100644
--- a/docs/api/datamodel.rst
+++ b/docs/api/datamodel.rst
@@ -485,6 +485,50 @@ GCODE analysis information
- 0..1
- Float
- The minimum Z coordinate of the printed model, in mm
+ * - ``travelArea``
+ - 0..1
+ - Object
+ - Information regarding the bounding box of all moves
+ * - ``travelArea.maxX``
+ - 0..1
+ - Float
+ - The maximum X coordinate of all moves, in mm
+ * - ``travelArea.maxY``
+ - 0..1
+ - Float
+ - The maximum Y coordinate of all moves, in mm
+ * - ``travelArea.maxZ``
+ - 0..1
+ - Float
+ - The maximum Z coordinate of all moves, in mm
+ * - ``travelArea.minX``
+ - 0..1
+ - Float
+ - The minimum X coordinate of all moves, in mm
+ * - ``travelArea.minY``
+ - 0..1
+ - Float
+ - The minimum Y coordinate of all moves, in mm
+ * - ``travelArea.minZ``
+ - 0..1
+ - Float
+ - The minimum Z coordinate of all moves, in mm
+ * - ``travelDimensions``
+ - 0..1
+ - Object
+ - Information regarding the size of the travel area
+ * - ``travelDimensions.depth``
+ - 0..1
+ - Float
+ - The depth of the travel area, in mm
+ * - ``travelDimensions.height``
+ - 0..1
+ - Float
+ - The height of the travel area, in mm
+ * - ``travelDimensions.width``
+ - 0..1
+ - Float
+ - The width of the travel area, in mm
.. _sec-api-datamodel-files-ref:
diff --git a/src/octoprint/cli/analysis.py b/src/octoprint/cli/analysis.py
index 297ccb83c..6ada2c807 100644
--- a/src/octoprint/cli/analysis.py
+++ b/src/octoprint/cli/analysis.py
@@ -13,7 +13,7 @@ click.disable_unicode_literals_warning = True
dimensions = ("depth", "height", "width")
-printing_area = ("maxX", "maxY", "maxZ", "minX", "minY", "minZ")
+area = ("maxX", "maxY", "maxZ", "minX", "minY", "minZ")
def empty_result(result):
@@ -37,15 +37,21 @@ def validate_result(result):
if "dimensions" not in result or not validate_dict(result["dimensions"], dimensions):
return False
+ if "travel_dimensions" not in result or not validate_dict(
+ result["travel_dimensions"], dimensions
+ ):
+ return False
+
if "extrusion_length" not in result or not validate_list(result["extrusion_length"]):
return False
if "extrusion_volume" not in result or not validate_list(result["extrusion_volume"]):
return False
- if "printing_area" not in result or not validate_dict(
- result["printing_area"], printing_area
- ):
+ if "printing_area" not in result or not validate_dict(result["printing_area"], area):
+ return False
+
+ if "travel_area" not in result or not validate_dict(result["travel_area"], area):
return False
if "total_time" not in result or invalid_float(result["total_time"]):
diff --git a/src/octoprint/filemanager/analysis.py b/src/octoprint/filemanager/analysis.py
index b6c01de93..6eea16c47 100644
--- a/src/octoprint/filemanager/analysis.py
+++ b/src/octoprint/filemanager/analysis.py
@@ -27,7 +27,16 @@ EMPTY_RESULT = {
"minZ": 0,
"maxZ": 0,
},
+ "travelArea": {
+ "minX": 0,
+ "maxX": 0,
+ "minY": 0,
+ "maxY": 0,
+ "minZ": 0,
+ "maxZ": 0,
+ },
"dimensions": {"width": 0, "height": 0, "depth": 0},
+ "travelDimensions": {"width": 0, "height": 0, "depth": 0},
"filament": {},
}
@@ -390,6 +399,28 @@ class GcodeAnalysisQueue(AbstractAnalysisQueue):
* Depth of the printed model along the Y axis, in mm
- * ``dimensions.height``
* Height of the printed model along the Z axis, in mm
+ - * ``travelArea``
+ * Bounding box of all machine movements (minimum and maximum coordinates)
+ - * ``travelArea.minX``
+ * Minimum X coordinate of the machine movement
+ - * ``travelArea.maxX``
+ * Maximum X coordinate of the machine movement
+ - * ``travelArea.minY``
+ * Minimum Y coordinate of the machine movement
+ - * ``travelArea.maxY``
+ * Maximum Y coordinate of the machine movement
+ - * ``travelArea.minZ``
+ * Minimum Z coordinate of the machine movement
+ - * ``travelArea.maxZ``
+ * Maximum Z coordinate of the machine movement
+ - * ``travelDimensions``
+ * Dimensions of the travel area in X, Y, Z
+ - * ``travelDimensions.width``
+ * Width of the travel area along the X axis, in mm
+ - * ``travelDimensions.depth``
+ * Depth of the travel area along the Y axis, in mm
+ - * ``travelDimensions.height``
+ * Height of the travel area along the Z axis, in mm
"""
def __init__(self, finished_callback):
@@ -406,7 +437,14 @@ class GcodeAnalysisQueue(AbstractAnalysisQueue):
if self._current.analysis and all(
map(
lambda x: x in self._current.analysis,
- ("printingArea", "dimensions", "estimatedPrintTime", "filament"),
+ (
+ "printingArea",
+ "dimensions",
+ "travelArea",
+ "travelDimensions",
+ "estimatedPrintTime",
+ "filament",
+ ),
)
):
return self._current.analysis
@@ -499,6 +537,8 @@ class GcodeAnalysisQueue(AbstractAnalysisQueue):
result["printingArea"] = analysis["printing_area"]
result["dimensions"] = analysis["dimensions"]
+ result["travelArea"] = analysis["travel_area"]
+ result["travelDimensions"] = analysis["travel_dimensions"]
if analysis["total_time"]:
result["estimatedPrintTime"] = analysis["total_time"] * 60
if analysis["extrusion_length"]:
diff --git a/src/octoprint/static/js/app/viewmodels/files.js b/src/octoprint/static/js/app/viewmodels/files.js
index e6f22a467..925c57d3c 100644
--- a/src/octoprint/static/js/app/viewmodels/files.js
+++ b/src/octoprint/static/js/app/viewmodels/files.js
@@ -1135,6 +1135,11 @@ $(function () {
return true;
}
+ var travelArea = data["gcodeAnalysis"]["travelArea"];
+ if (!travelArea) {
+ return true;
+ }
+
var printerProfile = self.printerProfiles.currentProfileData();
if (!printerProfile) {
return true;
@@ -1173,46 +1178,72 @@ $(function () {
}
}
- // model not within bounds, we need to prepare a warning
- var warning =
- "<p>" +
- _.sprintf(
- gettext(
- "Object in %(name)s exceeds the print volume of the currently selected printer profile, be careful when printing this."
- ),
- {name: _.escape(data.name)}
- ) +
- "</p>";
var info = "";
+ var objectFits = true;
+ var travelFits = true;
- var formatData = {
- profile: boundaries,
- object: printingArea
- };
+ function _area_exceeds_boundaries(ax, area) {
+ return (
+ area["min" + ax] < boundaries["min" + ax] ||
+ area["max" + ax] > boundaries["max" + ax]
+ );
+ }
- // find exceeded dimensions
- if (
- printingArea["minX"] < boundaries["minX"] ||
- printingArea["maxX"] > boundaries["maxX"]
- ) {
- info += gettext("Object exceeds print volume in width.<br>");
+ function _exceed_warning(culprit, dimension) {
+ return _.sprintf(
+ gettext("%(culprit)s exceeds print volume in %(dimension)s.<br>"),
+ {culprit: culprit, dimension: dimension}
+ );
}
- if (
- printingArea["minY"] < boundaries["minY"] ||
- printingArea["maxY"] > boundaries["maxY"]
- ) {
- info += gettext("Object exceeds print volume in depth.<br>");
+
+ // check if printing area exceeds boundaries
+ if (_area_exceeds_boundaries("X", printingArea)) {
+ info += _exceed_warning(gettext("Object"), gettext("width"));
+ objectFits = false;
}
- if (
- printingArea["minZ"] < boundaries["minZ"] ||
- printingArea["maxZ"] > boundaries["maxZ"]
- ) {
- info += gettext("Object exceeds print volume in height.<br>");
+ if (_area_exceeds_boundaries("Y", printingArea)) {
+ info += _exceed_warning(gettext("Object"), gettext("depth"));
+ objectFits = false;
+ }
+ if (_area_exceeds_boundaries("Z", printingArea)) {
+ info += _exceed_warning(gettext("Object"), gettext("height"));
+ objectFits = false;
+ }
+
+ // check if travel area exceeds boundaries
+ if (_area_exceeds_boundaries("X", travelArea)) {
+ info += _exceed_warning(gettext("Travel"), gettext("width"));
+ travelFits = false;
+ }
+ if (_area_exceeds_boundaries("Y", travelArea)) {
+ info += _exceed_warning(gettext("Travel"), gettext("depth"));
+ travelFits = false;
+ }
+ if (_area_exceeds_boundaries("Z", travelArea)) {
+ info += _exceed_warning(gettext("Travel"), gettext("height"));
+ travelFits = false;
}
- //warn user
- if (info !== "") {
+ if (travelFits && objectFits) {
+ return true;
+ } else {
+ // model not within bounds, we need to prepare a warning
if (notify) {
+ var formatData = {
+ name: _.escape(data.name),
+ profile: boundaries,
+ object: printingArea,
+ travel: travelArea,
+ culprit: !objectFits ? "Object" : "Travel area"
+ };
+
+ info += _.sprintf(
+ gettext(
+ "Travel area: (%(travel.minX).2f, %(travel.minY).2f, %(travel.minZ).2f) &times; (%(travel.maxX).2f, %(travel.maxY).2f, %(travel.maxZ).2f)"
+ ),
+ formatData
+ );
+ info += "<br>";
info += _.sprintf(
gettext(
"Object's bounding box: (%(object.minX).2f, %(object.minY).2f, %(object.minZ).2f) &times; (%(object.maxX).2f, %(object.maxY).2f, %(object.maxZ).2f)"
@@ -1227,21 +1258,34 @@ $(function () {
formatData
);
+ // prepare a warning message
+ var warning =
+ "<p>" +
+ _.sprintf(
+ gettext(
+ "%(culprit)s in %(name)s exceeds the print volume of the currently selected printer profile, be careful when printing this."
+ ),
+ formatData
+ ) +
+ "</p>";
+
warning += pnotifyAdditionalInfo(info);
warning +=
'<p><small>You can disable this check via Settings &gt; Features &gt; "Enable model size detection [...]"</small></p>';
+ //warn user
new PNotify({
- title: gettext("Object doesn't fit print volume"),
+ title: _.sprintf(
+ gettext("%(culprit)s exceeds print volume"),
+ formatData
+ ),
text: warning,
type: "warning",
hide: false
});
}
return false;
- } else {
- return true;
}
};
diff --git a/src/octoprint/util/gcodeInterpreter.py b/src/octoprint/util/gcodeInterpreter.py
index 3437b5c63..8f0773981 100644
--- a/src/octoprint/util/gcodeInterpreter.py
+++ b/src/octoprint/util/gcodeInterpreter.py
@@ -194,6 +194,22 @@ class MinMax3D:
setattr(result, c, value)
return result
+ @property
+ def dimensions(self):
+ size = self.size
+ return {"width": size.x, "depth": size.y, "height": size.z}
+
+ @property
+ def area(self):
+ return {
+ "minX": None if math.isinf(self.min.x) else self.min.x,
+ "minY": None if math.isinf(self.min.y) else self.min.y,
+ "minZ": None if math.isinf(self.min.z) else self.min.z,
+ "maxX": None if math.isinf(self.max.x) else self.max.x,
+ "maxY": None if math.isinf(self.max.y) else self.max.y,
+ "maxZ": None if math.isinf(self.max.z) else self.max.z,
+ }
+
class AnalysisAborted(Exception):
def __init__(self, reenqueue=True, *args, **kwargs):
@@ -217,7 +233,8 @@ class gcode:
self._abort = False
self._reenqueue = True
self._filamentDiameter = 0
- self._minMax = MinMax3D()
+ self._print_minMax = MinMax3D()
+ self._travel_minMax = MinMax3D()
self._progress_callback = progress_callback
self._incl_layers = incl_layers
@@ -249,19 +266,19 @@ class gcode:
@property
def dimensions(self):
- size = self._minMax.size
- return {"width": size.x, "depth": size.y, "height": size.z}
+ return self._print_minMax.dimensions
+
+ @property
+ def travel_dimensions(self):
+ return self._travel_minMax.dimensions
@property
def printing_area(self):
- return {
- "minX": None if math.isinf(self._minMax.min.x) else self._minMax.min.x,
- "minY": None if math.isinf(self._minMax.min.y) else self._minMax.min.y,
- "minZ": None if math.isinf(self._minMax.min.z) else self._minMax.min.z,
- "maxX": None if math.isinf(self._minMax.max.x) else self._minMax.max.x,
- "maxY": None if math.isinf(self._minMax.max.y) else self._minMax.max.y,
- "maxZ": None if math.isinf(self._minMax.max.z) else self._minMax.max.z,
- }
+ return self._print_minMax.area
+
+ @property
+ def travel_area(self):
+ return self._travel_minMax.area
@property
def layers(self):
@@ -291,7 +308,7 @@ class gcode:
g90_extruder=False,
bed_z=0.0,
):
- self._minMax.min.z = bed_z
+ self._print_minMax.min.z = self._travel_minMax.min.z = bed_z
if os.path.isfile(filename):
self.filename = filename
self._fileSize = os.stat(filename).st_size
@@ -463,12 +480,6 @@ class gcode:
else:
e -= currentE[currentExtruder]
- # If move with extrusion, calculate new min/max coordinates of model
- if e > 0 and move:
- # extrusion and move -> oldPos & pos relevant for print area & dimensions
- self._minMax.record(oldPos)
- self._minMax.record(pos)
-
totalExtrusion[currentExtruder] += e
currentE[currentExtruder] += e
maxExtrusion[currentExtruder] = max(
@@ -484,6 +495,15 @@ class gcode:
else:
e = 0
+ # If move, calculate new min/max coordinates
+ if move:
+ self._travel_minMax.record(oldPos)
+ self._travel_minMax.record(pos)
+ if e > 0:
+ # store as print move if extrusion is > 0
+ self._print_minMax.record(oldPos)
+ self._print_minMax.record(pos)
+
# move time in x, y, z, will be 0 if no movement happened
moveTimeXYZ = abs((oldPos - pos).length / feedrate)
@@ -567,15 +587,6 @@ class gcode:
else:
e -= currentE[currentExtruder]
- # If move with extrusion, calculate new min/max coordinates of model
- if e > 0 and move:
- # extrusion and move -> oldPos & pos relevant for print area & dimensions
- self._minMax.record(oldPos)
- self._minMax.record(pos)
- self._addArcMinMax(
- self._minMax, startAngle, endAngle, centerArc, r
- )
-
totalExtrusion[currentExtruder] += e
currentE[currentExtruder] += e
maxExtrusion[currentExtruder] = max(
@@ -591,6 +602,21 @@ class gcode:
else:
e = 0
+ # If move, calculate new min/max coordinates
+ if move:
+ self._travel_minMax.record(oldPos)
+ self._travel_minMax.record(pos)
+ self._addArcMinMax(
+ self._travel_minMax, startAngle, endAngle, centerArc, r
+ )
+ if e > 0:
+ # store as print move if extrusion is > 0
+ self._print_minMax.record(oldPos)
+ self._print_minMax.record(pos)
+ self._addArcMinMax(
+ self._print_minMax, startAngle, endAngle, centerArc, r
+ )
+
# calculate 3d arc length
arcLengthXYZ = math.sqrt((oldPos.z - pos.z) ** 2 + (arcAngle * r) ** 2)
@@ -806,6 +832,8 @@ class gcode:
"extrusion_volume": self.extrusionVolume,
"dimensions": self.dimensions,
"printing_area": self.printing_area,
+ "travel_dimensions": self.travel_dimensions,
+ "travel_area": self.travel_area,
}
if self._incl_layers:
result["layers"] = self.layers