__init__.py 9.76 KB
Newer Older
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
1
2
3
4
5
6
7
8
9
10
11
# coding=utf-8
from __future__ import absolute_import

### (Don't forget to remove me)
# This is a basic skeleton for your plugin's __init__.py. You probably want to adjust the class name of your plugin
# as well as the plugin mixins it's subclassing from. This is really just a basic skeleton to get you started,
# defining your plugin as a template plugin, settings and asset plugin. Feel free to add or remove mixins
# as necessary.
#
# Take a look at the documentation on what other plugin mixins are available.

12
import subprocess
13
14
import time
import threading
15

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
16
17
import octoprint.plugin
import sys
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
18
import os
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
19

20
import gcodeparser as gcp
21

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
22
23
import flask

24
25
sampling = 0

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
26
27
28
29
30
31
class AutofdpPlugin(octoprint.plugin.SettingsPlugin,
                    octoprint.plugin.AssetPlugin,
                    octoprint.plugin.TemplatePlugin,
		    octoprint.plugin.StartupPlugin,
		    octoprint.plugin.EventHandlerPlugin,
		    octoprint.plugin.SimpleApiPlugin):
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# ------------------------------------------------------------------------- #
#			   AUTO FDP CUSTOM FUNCTIONS			    #
#		Implement custom logic and driving code here		    #
# ------------------------------------------------------------------------- #

	def send_layer(self, layer):
		""" Sends a layer to the Octoprint client

		Parameters
		----------
		layer: gcodeparser.Layer
			Layer object to send to the client.
		"""
		data = dict(layer_svg=self.model.layers[layer].to_svg_inline(500,
			    self.model.max_x, self.model.max_y),
			    num_layers=len(self.model.layers),
			    curr_layer=self.curr_layer+1)
		self._plugin_manager.send_plugin_message(self._identifier, data)

	def process_gcode(self, payload):
		""" Process a gcode file

		Parameters
		----------
		payload: dict
			File selected payload from the on-event handler.
		"""
59
60
61
62
63
		num_pts = int(self._settings.get(['sampling_points']))
		spacing = int(self._settings.get(['sampling_freq']))
		method = self._settings.get(['sampling_algo'])
		self._logger.info('Processing GCODE with {} layer spacing, {} samples per layer and {}'.format(spacing, num_pts, method))

64
		full_path = os.path.expanduser("~/.octoprint/uploads/{}".format(payload['name']))
65
		self.model = gcp.parse_gcode(full_path,spacing, num_pts, method)
66
67
68
		self.num_layers = len(self.model.layers)
		self.curr_layer = 0;

69
	def sample_layer(self, current_layer):
70
71
72
		""" Sample a layer

		""" 
73
74
75
76
77
78
79
		layer = []
		next_height = []
		if current_layer != 0:
			layer = self.model.layers[current_layer-1]
			next_height = self.model.layers[current_layer].z_height
		else:
			return True
80

81
82
83
		def probe_process(self, layer, next_height):
			self._logger.info("Starting sampling run at Z={}".format(layer.z_height))
			result = True
84
			for point in zip(layer.sample_points['x'], layer.sample_points['y']):
85
86
				if self.probe_point(point[0], point[1], layer.z_height):
					self._logger.info("Good reading at X={}, Y={}".format(point[0], point[1]))
87
				else:
88
89
90
91
					self._logger.info("Bad reading at X={}, Y={}".format(point[0], point[1]))
					result = False
					break
			if result:
92
93
				self._printer.extrude(float(self._settings.get(['retraction'])), None, None)
				pass
94
			else:
95
96
97
98
99
100
101
102
103
104
				self._printer.pause_print()
#			self._printer.commands("G0 Z{}".format(next_height))
			self._printer.set_job_on_hold(False)

		if len(layer.sample_points['x']):
			if self._printer.set_job_on_hold(True):
				thread = threading.Thread(target=probe_process, args=[self,layer,next_height])
				thread.daemon = True
				thread.start()
			return None,
105
106
		else:
			return True
107
108
109
110
111
112
113
114
115
116
117

	def probe_point(self, x, y, z):
		"""
		Probe at selected x,y,z position. Move probe to point, extend probe, measure output, and report result
		"""
		self._logger.info('STARTING PROBE SEQUENCE')

		probe_x = x - self.offsets['x']
		probe_y = y - self.offsets['y']
		probe_z = z + self.offsets['z']

118
119
120
121
		if (probe_x < 0) or (probe_y < 0) or (probe_z < 0):
			self._logger.info('Sample point outside of printer bounds. Assuming good')
			return True

122
123
124
125
126
127
128
129
130
		# Go to the sample position, accounting for tool offset
		command = 'G0 F4329 X{} Y{}'.format(probe_x, probe_y)
		self._logger.info(command)
		self._printer.commands(command)
		self._printer.commands('G4 P0')
		self._printer.commands('M114')
		self.movement_lock = True
		while(self.movement_lock):
			pass
131

132
		reading = subprocess.run("./sample.sh", capture_output=True).stdout.decode("utf-8").strip() == '0'
133

134
		return reading
135
136
137
138
139

# ------------------------------------------------------------------------- #
#			 OCTOPRINT DEFINED FUNCTIONS			    #
#		Functions and event callbacks from octoprint		    #
# ------------------------------------------------------------------------- #
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
140
141
142
143
144
145

	def __init__(self):
		self.model = []
		self.curr_layer = 0
		self.num_layers = 0

146
147
148
149
		self.sample_lock = threading.Lock()

		self.movement_lock = False

150
151
		self.offsets = dict([('x', 0), ('y', 0), ('z', 0)])

152
153
		self.file_payload = []

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
154
155
156
157
	##~~ SimpleApiPlugin mixin
	def get_api_commands(self):
		return dict(
			change_layer=["inc"],
158
159
			test_command=[],
			reload_model=[]
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
160
161
162
163
164
165
166
167
168
169
		)

	def on_api_command(self, command, data):
		if command == "change_layer":
			if data["inc"] == "+1" and self.curr_layer < self.num_layers - 1:
				self.curr_layer += 1
				self.send_layer(self.curr_layer)
			if data["inc"] == "-1" and self.curr_layer > 0:
				self.curr_layer -= 1
				self.send_layer(self.curr_layer)
170
		elif command == "reload_model":
171
172
173
174
175
			if self.file_payload:
				self._logger.info('reloading model')
				self.on_event('FileSelected', self.file_payload)
			else:
				self._logger.info('no GCODE selected')
176
		elif command == "test_command":
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
177
178
179
180
			self._logger.info(data)

	##~~ EventHandlerPlugin mixin
	def on_event(self, event, payload):
181
		global sampling
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
182
183
		if event == 'FileSelected':
			self._logger.info('File selected: "{}" at path "{}"'.format(payload['name'], payload['path']))
184
			self.file_payload = payload
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
185
186
187
			self.process_gcode(payload)
			self._logger.info('GCODE Processed: {} layers'.format(self.num_layers))
			self.send_layer(0)
188
		if event == 'ZChange' and self._settings.get(['enable'])==True:
189
			data = self._printer.get_current_data()
190
			self._logger.info(data)
191
			currentZ = data['currentZ']
192
			try:
193
194
				current_layer = self.model.layer_heights.index(currentZ)
				self._logger.info('Current layer: {}'.format(current_layer))
195
196
197
				self._printer.extrude(float(self._settings.get(['retraction'])), None, None)
				self.sample_layer(current_layer)
				self._logger.info('Finished sampling run')
198
199
			except:
				self._logger.info('Not a valid layer at z = {}'.format(currentZ))
200
				return
201
202
203
		if event == 'PositionUpdate' and self._settings.get(['enable'])==True:
			if self.movement_lock == True:
				self.movement_lock = False
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
204
205
206
207
208
209
210
211

	##~~ TemplatePlugin mixin

	def get_template_configs(self):
		return [
			dict(type="tab", custom_bindings=True),
			dict(type="settings", custom_bindings=False)
		]
212

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
213
	def on_after_startup(self):
214
		self._logger.info(os.getcwd())
215
216
217
218
		self._logger.info("autoFDP starting, retrieving saved settings...")
		self.offsets['x'] = float(self._settings.get(['x_offset']))
		self.offsets['y'] = float(self._settings.get(['y_offset']))
		self.offsets['z'] = float(self._settings.get(['z_offset']))
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
219
220
221
222
223
224

	##~~ SettingsPlugin mixin

	def get_settings_defaults(self):
		return dict(
			# put your plugin's default settings here
225
			enable="False",
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
226
227
			x_offset="0",
			y_offset="0",
228
229
230
231
232
			z_offset="0",
			retraction="0",
			sampling_algo="Random sampling",
			sampling_freq="10",
			sampling_points="5"
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
233
234
235
236
237
		)

	def on_settings_save(self, data):
		octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
		self._logger.info("SETTINGS HAVE BEEN CHANGED")
238
239
		self._logger.info(self._settings.get(['enable']))
		self._logger.info(self._settings.get(['sampling_algo']))
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
240
		#TODO: PROBE UPDATE CODE HERE
241
242
243
		self.offsets['x'] = float(self._settings.get(['x_offset']))
		self.offsets['y'] = float(self._settings.get(['y_offset']))
		self.offsets['z'] = float(self._settings.get(['z_offset']))
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
244

245
246
		self._logger.info(self._settings.get(['enable']) == "True")
		self._logger.info(self._settings.get(['enable']) == True)
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
247
248
249
250
251
252
253
254
255
	##~~ AssetPlugin mixin
	def get_assets(self):
		# Define your plugin's asset files to automatically include in the
		# core UI here.
		return dict(
			js=["js/autoFDP.js"],
			#css=["css/autoFDP.css"],
			#less=["less/autoFDP.less"]
		)
256

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
	##~~ Softwareupdate hook

	def get_update_information(self):
		# Define the configuration for your plugin to use with the Software Update
		# Plugin here. See https://docs.octoprint.org/en/master/bundledplugins/softwareupdate.html
		# for details.
		return dict(
			autoFDP=dict(
				displayName="Autofdp Plugin",
				displayVersion=self._plugin_version,

				# version check: github repository
				type="github_release",
				user="you",
				repo="OctoPrint-Autofdp",
				current=self._plugin_version,

				# update method: pip
				pip="https://github.com/you/OctoPrint-Autofdp/archive/{target_version}.zip"
			)
		)


# If you want your plugin to be registered within OctoPrint under a different name than what you defined in setup.py
# ("OctoPrint-PluginSkeleton"), you may define that here. Same goes for the other metadata derived from setup.py that
# can be overwritten via __plugin_xyz__ control properties. See the documentation for that.
__plugin_name__ = "AutoFDP Plugin"

# Starting with OctoPrint 1.4.0 OctoPrint will also support to run under Python 3 in addition to the deprecated
# Python 2. New plugins should make sure to run under both versions for now. Uncomment one of the following
# compatibility flags according to what Python versions your plugin supports!
#__plugin_pythoncompat__ = ">=2.7,<3" # only python 2
#__plugin_pythoncompat__ = ">=3,<4" # only python 3
__plugin_pythoncompat__ = ">=2.7,<4" # python 2 and 3

def __plugin_load__():
	global __plugin_implementation__
	__plugin_implementation__ = AutofdpPlugin()

	global __plugin_hooks__
	__plugin_hooks__ = {
		"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
	}