Skip to content

Commit 6ba8cb4

Browse files
authored
0.1.12
#### Added - Rotation options to mesh visualization, community forum request. - Custom debug logging for future troubleshooting - Custom commands that will give additional buttons on tab - Cancel button to stop processing when visualization gets stuck - Larger precision number processing - Themify support, will now color the background and axis based on tab colors - Marlin Mesh Bed Leveling support - Marlin OpenScad Output support - Option to show webcam while processing #### Updated - Several UI improvements - Timeout logic to also stop processing on server side - Plotly library to version 1.52.2
1 parent bf814d0 commit 6ba8cb4

35 files changed

+12006
-60
lines changed

octoprint_bedlevelvisualizer/__init__.py

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,28 @@
44
import octoprint.plugin
55
import re
66
import numpy as np
7+
import logging
8+
import flask
79

810
class bedlevelvisualizer(octoprint.plugin.StartupPlugin,
911
octoprint.plugin.TemplatePlugin,
1012
octoprint.plugin.AssetPlugin,
11-
octoprint.plugin.SettingsPlugin,
12-
octoprint.plugin.WizardPlugin):
13+
octoprint.plugin.SettingsPlugin,
14+
octoprint.plugin.WizardPlugin,
15+
octoprint.plugin.SimpleApiPlugin):
1316

1417
def __init__(self):
1518
self.processing = False
19+
self.mesh_collection_canceled = False
1620
self.old_marlin = False
1721
self.old_marlin_offset = 0
1822
self.repetier_firmware = False
1923
self.mesh = []
2024
self.box = []
2125
self.flip_x = False
2226
self.flip_y = False
27+
self._logger = logging.getLogger("octoprint.plugins.bedlevelvisualizer")
28+
self._bedlevelvisualizer_logger = logging.getLogger("octoprint.plugins.bedlevelvisualizer.debug")
2329

2430
##~~ SettingsPlugin
2531
def get_settings_defaults(self):
@@ -36,16 +42,61 @@ def get_settings_defaults(self):
3642
use_center_origin=False,
3743
use_relative_offsets=False,
3844
timeout=60,
39-
ignore_correction_matrix=False)
45+
rotation=0,
46+
ignore_correction_matrix=False,
47+
debug_logging = False,
48+
commands=[],
49+
show_labels=True,
50+
show_webcam=False)
51+
52+
def get_settings_version(self):
53+
return 1
54+
55+
def on_settings_migrate(self, target, current=None):
56+
if current is None or current < 1:
57+
# Loop through commands adding new fields
58+
commands_new = []
59+
self._logger.info(self._settings.get(['commands']))
60+
for command in self._settings.get(['commands']):
61+
command["confirmation"] = False
62+
command["input"] = []
63+
command["message"] = ""
64+
commands_new.append(command)
65+
self._settings.set(["commands"],commands_new)
66+
67+
def on_settings_save(self, data):
68+
old_debug_logging = self._settings.get_boolean(["debug_logging"])
69+
70+
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
71+
72+
new_debug_logging = self._settings.get_boolean(["debug_logging"])
73+
if old_debug_logging != new_debug_logging:
74+
if new_debug_logging:
75+
self._bedlevelvisualizer_logger.setLevel(logging.DEBUG)
76+
else:
77+
self._bedlevelvisualizer_logger.setLevel(logging.INFO)
4078

4179
##~~ StartupPlugin
80+
81+
def on_startup(self, host, port):
82+
# setup customized logger
83+
from octoprint.logging.handlers import CleaningTimedRotatingFileHandler
84+
bedlevelvisualizer_logging_handler = CleaningTimedRotatingFileHandler(self._settings.get_plugin_logfile_path(postfix="debug"), when="D", backupCount=3)
85+
bedlevelvisualizer_logging_handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s"))
86+
bedlevelvisualizer_logging_handler.setLevel(logging.DEBUG)
87+
88+
self._bedlevelvisualizer_logger.addHandler(bedlevelvisualizer_logging_handler)
89+
self._bedlevelvisualizer_logger.setLevel(logging.DEBUG if self._settings.get_boolean(["debug_logging"]) else logging.INFO)
90+
self._bedlevelvisualizer_logger.propagate = False
91+
4292
def on_after_startup(self):
4393
self._logger.info("OctoPrint-BedLevelVisualizer loaded!")
4494

4595
##~~ AssetPlugin
4696
def get_assets(self):
4797
return dict(
48-
js=["js/bedlevelvisualizer.js","js/plotly-latest.min.js"],
98+
js=["js/jquery-ui.min.js","js/knockout-sortable.js","js/fontawesome-iconpicker.js","js/ko.iconpicker.js","js/plotly-latest.min.js","js/bedlevelvisualizer.js"],
99+
css=["css/font-awesome.min.css","css/font-awesome-v4-shims.min.css","css/fontawesome-iconpicker.css","css/bedlevelvisualizer.css"]
49100
)
50101

51102
##~~ WizardPlugin
@@ -69,20 +120,30 @@ def flagMeshCollection(self, comm_instance, phase, cmd, cmd_type, gcode, *args,
69120
if cmd.startswith("@BEDLEVELVISUALIZER"):
70121
self.mesh = []
71122
self.box = []
72-
self.processing = True
123+
if not self.mesh_collection_canceled and not self.processing:
124+
self.processing = True
125+
if self.mesh_collection_canceled:
126+
self.mesh_collection_canceled = False
127+
return
128+
self._bedlevelvisualizer_logger.debug("mesh collection started")
73129
return
74130

75131
def processGCODE(self, comm, line, *args, **kwargs):
76-
if self._settings.get_boolean(["ignore_correction_matrix"]) and re.match(r"^Bed Level Correction Matrix:.*$", line.strip()):
132+
if self.processing == True:
133+
self._bedlevelvisualizer_logger.debug(line.strip())
134+
if self._settings.get_boolean(["ignore_correction_matrix"]) and re.match(r"^(Mesh )?Bed Level (Correction Matrix|data):.*$", line.strip()):
77135
line = "ok"
78-
if self.processing and "ok" not in line and re.match(r"^((G33.+)|(Bed.+)|(\d+\s)|(\|\s*)|(\[?\s?\+?\-?\d?\.\d+\]?\s*\,?)|(\s?\.\s*)|(NAN\,?))+$", line.strip()):
136+
if self.processing and "ok" not in line and re.match(r"^((G33.+)|(Bed.+)|(\d+\s)|(\|\s*)|(\s*\[\s+)|(\[?\s?\+?\-?\d?\.\d+\]?\s*\,?)|(\s?\.\s*)|(NAN\,?))+(\s+\],?)?$", line.strip()):
79137
new_line = re.findall(r"(\+?\-?\d*\.\d*)",line)
138+
self._bedlevelvisualizer_logger.debug(new_line)
80139

81140
if re.match(r"^Bed x:.+$", line.strip()):
82141
self.old_marlin = True
142+
self._bedlevelvisualizer_logger.debug("using old marlin flag")
83143

84144
if re.match(r"^G33 X.+$", line.strip()):
85145
self.repetier_firmware = True
146+
self._bedlevelvisualizer_logger.debug("using repetier flag")
86147

87148
if self._settings.get(["stripFirst"]):
88149
new_line.pop(0)
@@ -93,6 +154,7 @@ def processGCODE(self, comm, line, *args, **kwargs):
93154
return line
94155

95156
if self.processing and "ok" not in line and re.match(r"^Subdivided with CATMULL ROM Leveling Grid:.*$", line.strip()):
157+
self._bedlevelvisualizer_logger.debug("resetting mesh to blank because of CATMULL subdivision")
96158
self.mesh = []
97159
return line
98160

@@ -110,8 +172,11 @@ def processGCODE(self, comm, line, *args, **kwargs):
110172

111173
if self.processing and self.old_marlin and re.match(r"^Eqn coefficients:.+$", line.strip()):
112174
self.old_marlin_offset = re.sub("^(Eqn coefficients:.+)(\d+.\d+)$",r"\2", line.strip())
175+
self._bedlevelvisualizer_logger.debug("using old marlin offset")
113176

114-
if self.processing and "Home XYZ first" in line:
177+
if self.processing and "Home XYZ first" in line or "Invalid mesh" in line:
178+
reason = "data is invalid" if "Invalid" in line else "homing required"
179+
self._bedlevelvisualizer_logger.debug("stopping mesh collection because %s" % reason)
115180
self._plugin_manager.send_plugin_message(self._identifier, dict(error=line.strip()))
116181
self.processing = False
117182
return line
@@ -143,38 +208,67 @@ def processGCODE(self, comm, line, *args, **kwargs):
143208
max_y = max([y for x, y in self.box])
144209

145210
bed = dict(type=bed_type,x_min=min_x,x_max=max_x,y_min=min_y,y_max=max_y,z_min=min_z,z_max=max_z)
211+
self._bedlevelvisualizer_logger.debug(bed)
146212

147213
if self.old_marlin or self.repetier_firmware:
148214
a = np.swapaxes(self.mesh,1,0)
149215
x = np.unique(a[0]).astype(np.float)
150216
y = np.unique(a[1]).astype(np.float)
151217
z = a[2].reshape((len(x),len(y)))
152-
self._logger.debug(a)
153-
self._logger.debug(x)
154-
self._logger.debug(y)
155-
self._logger.debug(z)
218+
self._bedlevelvisualizer_logger.debug(a)
219+
self._bedlevelvisualizer_logger.debug(x)
220+
self._bedlevelvisualizer_logger.debug(y)
221+
self._bedlevelvisualizer_logger.debug(z)
156222
offset = 0
157223
if self.old_marlin:
158224
offset = self.old_marlin_offset
159-
self._logger.debug(offset)
225+
self._bedlevelvisualizer_logger.debug(offset)
160226
self.mesh = np.subtract(z, [offset], dtype=np.float, casting='unsafe').tolist()
161-
self._logger.debug(self.mesh)
227+
self._bedlevelvisualizer_logger.debug(self.mesh)
162228

163229
self.processing = False
230+
self._bedlevelvisualizer_logger.debug("stopping mesh collection")
164231
if bool(self.flip_y) != bool(self._settings.get(["flipY"])):
232+
self._bedlevelvisualizer_logger.debug("flipping y axis")
165233
self.mesh.reverse()
166234

167235
if self._settings.get(["use_relative_offsets"]):
236+
self._bedlevelvisualizer_logger.debug("using relative offsets")
168237
self.mesh = np.array(self.mesh)
169238
if self._settings.get(["use_center_origin"]):
239+
self._bedlevelvisualizer_logger.debug("using center origin")
170240
self.mesh = np.subtract(self.mesh, self.mesh[len(self.mesh[0])/2,len(self.mesh)/2], dtype=np.float, casting='unsafe').tolist()
171241
else:
172242
self.mesh = np.subtract(self.mesh, self.mesh[0,0], dtype=np.float, casting='unsafe').tolist()
173243

244+
if int(self._settings.get(["rotation"])) > 0:
245+
self._bedlevelvisualizer_logger.debug("rotating mesh by %s" % self._settings.get(["rotation"]))
246+
self.mesh = np.array(self.mesh)
247+
self.mesh = np.rot90(self.mesh, int(self._settings.get(["rotation"]))/90).tolist()
248+
249+
self._bedlevelvisualizer_logger.debug(self.mesh)
250+
174251
self._plugin_manager.send_plugin_message(self._identifier, dict(mesh=self.mesh,bed=bed))
175252

176253
return line
177254

255+
##~~ SimpleApiPlugin mixin
256+
def get_api_commands(self):
257+
return dict(stopProcessing=[])
258+
259+
def on_api_get(self, request):
260+
if request.args.get("stopProcessing"):
261+
self._bedlevelvisualizer_logger.debug("Canceling mesh collection per user request")
262+
self._bedlevelvisualizer_logger.debug("Mesh data collected prior to cancel:")
263+
self._bedlevelvisualizer_logger.debug(self.mesh)
264+
self.processing = False
265+
self.mesh_collection_canceled = True
266+
self.mesh = []
267+
self._bedlevelvisualizer_logger.debug("Mesh data after clearing:")
268+
self._bedlevelvisualizer_logger.debug(self.mesh)
269+
response = dict(stopped=True)
270+
return flask.jsonify(response)
271+
178272
##~~ Softwareupdate hook
179273
def get_update_information(self):
180274
return dict(
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
div#bedlevelvisualizergraph div.modebar {
2+
left: 50%;
3+
transform: translateX(-50%);
4+
}
5+
6+
#bedlevelvisualizer_webcam_container{
7+
width:100%;
8+
overflow: hidden;
9+
position:relative;
10+
outline:0;
11+
background-color:#000;
12+
margin-bottom: 10px;
13+
}
14+
#bedlevelvisualizer_webcam_container .webcam_rotated{
15+
position:relative;
16+
width:100%;
17+
padding-bottom:100%;
18+
pointer-events:none;
19+
}
20+
#bedlevelvisualizer_webcam_container .webcam_rotated .webcam_fixed_ratio{
21+
position:absolute;
22+
transform:rotate(-90deg);
23+
top:0;
24+
bottom:0;
25+
pointer-events:none;
26+
}
27+
#bedlevelvisualizer_webcam_container .webcam_rotated .webcam_fixed_ratio .webcam_fixed_ratio_inner{
28+
width:100%;
29+
height:100%;
30+
pointer-events:none;
31+
}
32+
#bedlevelvisualizer_webcam_container .webcam_unrotated .webcam_fixed_ratio{
33+
width:100%;
34+
pointer-events:none;
35+
padding-bottom:100%;
36+
position:relative;
37+
}
38+
#bedlevelvisualizer_webcam_container .webcam_unrotated .webcam_fixed_ratio.ratio43{
39+
padding-bottom:75%;
40+
}
41+
#bedlevelvisualizer_webcam_container .webcam_unrotated .webcam_fixed_ratio.ratio169{
42+
padding-bottom:56.25%;
43+
}
44+
#bedlevelvisualizer_webcam_container .webcam_unrotated .webcam_fixed_ratio.ratio1610{
45+
padding-bottom:62.5%;
46+
}
47+
#bedlevelvisualizer_webcam_container .webcam_unrotated .webcam_fixed_ratio .webcam_fixed_ratio_inner{
48+
position:absolute;
49+
top:0;
50+
bottom:0;
51+
left:0;
52+
right:0;
53+
pointer-events:none;
54+
}
55+
#bedlevelvisualizer_webcam_container img{
56+
width:100%;
57+
height:100%;
58+
object-fit:contain;
59+
}

octoprint_bedlevelvisualizer/static/css/font-awesome-v4-shims.min.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

octoprint_bedlevelvisualizer/static/css/font-awesome.min.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)