diff options
author | Kestin Goforth <kgoforth1503@gmail.com> | 2022-09-28 15:36:48 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-28 15:36:48 +0300 |
commit | 4c86459c1bb2cd72152ffad37bf5ad05b9159276 (patch) | |
tree | ffe282582f3acc98629d0aeaf445550625c2987d | |
parent | ffdda85c39ae1cfad2211accb40ca44317fc9d45 (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.rst | 44 | ||||
-rw-r--r-- | src/octoprint/cli/analysis.py | 14 | ||||
-rw-r--r-- | src/octoprint/filemanager/analysis.py | 42 | ||||
-rw-r--r-- | src/octoprint/static/js/app/viewmodels/files.js | 114 | ||||
-rw-r--r-- | src/octoprint/util/gcodeInterpreter.py | 82 |
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) × (%(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) × (%(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 > Features > "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 |