__init__.py 9.81 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
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
21
import sampler
22

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

25
26
sampling = 0

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
27
28
29
30
31
32
class AutofdpPlugin(octoprint.plugin.SettingsPlugin,
                    octoprint.plugin.AssetPlugin,
                    octoprint.plugin.TemplatePlugin,
		    octoprint.plugin.StartupPlugin,
		    octoprint.plugin.EventHandlerPlugin,
		    octoprint.plugin.SimpleApiPlugin):
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
59
# ------------------------------------------------------------------------- #
#			   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.
		"""
60
61
62
63
64
		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))

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

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

		""" 
74
75
76
77
78
79
80
		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
81

82
83
84
		def probe_process(self, layer, next_height):
			self._logger.info("Starting sampling run at Z={}".format(layer.z_height))
			result = True
85
			for point in zip(layer.sample_points['x'], layer.sample_points['y']):
86
87
				if self.probe_point(point[0], point[1], layer.z_height):
					self._logger.info("Good reading at X={}, Y={}".format(point[0], point[1]))
88
				else:
89
90
91
92
					self._logger.info("Bad reading at X={}, Y={}".format(point[0], point[1]))
					result = False
					break
			if result:
93
94
				self._printer.extrude(float(self._settings.get(['retraction'])), None, None)
				pass
95
			else:
96
97
98
99
100
101
102
103
104
105
				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,
106
107
		else:
			return True
108
109
110
111
112
113
114
115
116
117
118

	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']

119
120
121
122
		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

123
124
125
126
127
128
129
130
131
		# 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
132

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
133
134
		reading = sampler.sample_probe(7, 0, 0.15)
			  #subprocess.run("./sample.sh", capture_output=True).stdout.decode("utf-8").strip() == '0'
135
		return reading
136
137
138
139
140

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

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

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

		self.movement_lock = False

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

153
154
		self.file_payload = []

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

	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)
171
		elif command == "reload_model":
172
173
174
175
176
			if self.file_payload:
				self._logger.info('reloading model')
				self.on_event('FileSelected', self.file_payload)
			else:
				self._logger.info('no GCODE selected')
177
		elif command == "test_command":
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
178
179
180
181
			self._logger.info(data)

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

	##~~ TemplatePlugin mixin

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

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
214
	def on_after_startup(self):
215
		self._logger.info(os.getcwd())
216
217
218
219
		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
220
221
222
223
224
225

	##~~ SettingsPlugin mixin

	def get_settings_defaults(self):
		return dict(
			# put your plugin's default settings here
226
			enable="False",
Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
227
228
			x_offset="0",
			y_offset="0",
229
230
231
232
233
			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
234
235
236
237
238
		)

	def on_settings_save(self, data):
		octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
		self._logger.info("SETTINGS HAVE BEEN CHANGED")
239
240
		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
241
		#TODO: PROBE UPDATE CODE HERE
242
243
244
		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
245

246
247
		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
248
249
250
251
252
253
254
255
256
	##~~ 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"]
		)
257

Nicholas Gar Hei Chan's avatar
Nicholas Gar Hei Chan committed
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
301
	##~~ 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
	}