4
4
import octoprint .plugin
5
5
import re
6
6
import numpy as np
7
+ import logging
8
+ import flask
7
9
8
10
class bedlevelvisualizer (octoprint .plugin .StartupPlugin ,
9
11
octoprint .plugin .TemplatePlugin ,
10
12
octoprint .plugin .AssetPlugin ,
11
- octoprint .plugin .SettingsPlugin ,
12
- octoprint .plugin .WizardPlugin ):
13
+ octoprint .plugin .SettingsPlugin ,
14
+ octoprint .plugin .WizardPlugin ,
15
+ octoprint .plugin .SimpleApiPlugin ):
13
16
14
17
def __init__ (self ):
15
18
self .processing = False
19
+ self .mesh_collection_canceled = False
16
20
self .old_marlin = False
17
21
self .old_marlin_offset = 0
18
22
self .repetier_firmware = False
19
23
self .mesh = []
20
24
self .box = []
21
25
self .flip_x = False
22
26
self .flip_y = False
27
+ self ._logger = logging .getLogger ("octoprint.plugins.bedlevelvisualizer" )
28
+ self ._bedlevelvisualizer_logger = logging .getLogger ("octoprint.plugins.bedlevelvisualizer.debug" )
23
29
24
30
##~~ SettingsPlugin
25
31
def get_settings_defaults (self ):
@@ -36,16 +42,61 @@ def get_settings_defaults(self):
36
42
use_center_origin = False ,
37
43
use_relative_offsets = False ,
38
44
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 )
40
78
41
79
##~~ 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
+
42
92
def on_after_startup (self ):
43
93
self ._logger .info ("OctoPrint-BedLevelVisualizer loaded!" )
44
94
45
95
##~~ AssetPlugin
46
96
def get_assets (self ):
47
97
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" ]
49
100
)
50
101
51
102
##~~ WizardPlugin
@@ -69,20 +120,30 @@ def flagMeshCollection(self, comm_instance, phase, cmd, cmd_type, gcode, *args,
69
120
if cmd .startswith ("@BEDLEVELVISUALIZER" ):
70
121
self .mesh = []
71
122
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" )
73
129
return
74
130
75
131
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 ()):
77
135
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 ()):
79
137
new_line = re .findall (r"(\+?\-?\d*\.\d*)" ,line )
138
+ self ._bedlevelvisualizer_logger .debug (new_line )
80
139
81
140
if re .match (r"^Bed x:.+$" , line .strip ()):
82
141
self .old_marlin = True
142
+ self ._bedlevelvisualizer_logger .debug ("using old marlin flag" )
83
143
84
144
if re .match (r"^G33 X.+$" , line .strip ()):
85
145
self .repetier_firmware = True
146
+ self ._bedlevelvisualizer_logger .debug ("using repetier flag" )
86
147
87
148
if self ._settings .get (["stripFirst" ]):
88
149
new_line .pop (0 )
@@ -93,6 +154,7 @@ def processGCODE(self, comm, line, *args, **kwargs):
93
154
return line
94
155
95
156
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" )
96
158
self .mesh = []
97
159
return line
98
160
@@ -110,8 +172,11 @@ def processGCODE(self, comm, line, *args, **kwargs):
110
172
111
173
if self .processing and self .old_marlin and re .match (r"^Eqn coefficients:.+$" , line .strip ()):
112
174
self .old_marlin_offset = re .sub ("^(Eqn coefficients:.+)(\d+.\d+)$" ,r"\2" , line .strip ())
175
+ self ._bedlevelvisualizer_logger .debug ("using old marlin offset" )
113
176
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 )
115
180
self ._plugin_manager .send_plugin_message (self ._identifier , dict (error = line .strip ()))
116
181
self .processing = False
117
182
return line
@@ -143,38 +208,67 @@ def processGCODE(self, comm, line, *args, **kwargs):
143
208
max_y = max ([y for x , y in self .box ])
144
209
145
210
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 )
146
212
147
213
if self .old_marlin or self .repetier_firmware :
148
214
a = np .swapaxes (self .mesh ,1 ,0 )
149
215
x = np .unique (a [0 ]).astype (np .float )
150
216
y = np .unique (a [1 ]).astype (np .float )
151
217
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 )
156
222
offset = 0
157
223
if self .old_marlin :
158
224
offset = self .old_marlin_offset
159
- self ._logger .debug (offset )
225
+ self ._bedlevelvisualizer_logger .debug (offset )
160
226
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 )
162
228
163
229
self .processing = False
230
+ self ._bedlevelvisualizer_logger .debug ("stopping mesh collection" )
164
231
if bool (self .flip_y ) != bool (self ._settings .get (["flipY" ])):
232
+ self ._bedlevelvisualizer_logger .debug ("flipping y axis" )
165
233
self .mesh .reverse ()
166
234
167
235
if self ._settings .get (["use_relative_offsets" ]):
236
+ self ._bedlevelvisualizer_logger .debug ("using relative offsets" )
168
237
self .mesh = np .array (self .mesh )
169
238
if self ._settings .get (["use_center_origin" ]):
239
+ self ._bedlevelvisualizer_logger .debug ("using center origin" )
170
240
self .mesh = np .subtract (self .mesh , self .mesh [len (self .mesh [0 ])/ 2 ,len (self .mesh )/ 2 ], dtype = np .float , casting = 'unsafe' ).tolist ()
171
241
else :
172
242
self .mesh = np .subtract (self .mesh , self .mesh [0 ,0 ], dtype = np .float , casting = 'unsafe' ).tolist ()
173
243
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
+
174
251
self ._plugin_manager .send_plugin_message (self ._identifier , dict (mesh = self .mesh ,bed = bed ))
175
252
176
253
return line
177
254
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
+
178
272
##~~ Softwareupdate hook
179
273
def get_update_information (self ):
180
274
return dict (
0 commit comments