merge
This commit is contained in:
180
idm.py
180
idm.py
@@ -1,4 +1,3 @@
|
|||||||
# Based upon the fantastic Beacon eddy current scanner support
|
|
||||||
#
|
#
|
||||||
# Copyright (C) 2020-2023 Matt Baker <baker.matt.j@gmail.com>
|
# Copyright (C) 2020-2023 Matt Baker <baker.matt.j@gmail.com>
|
||||||
# Copyright (C) 2020-2023 Lasse Dalegaard <dalegaard@gmail.com>
|
# Copyright (C) 2020-2023 Lasse Dalegaard <dalegaard@gmail.com>
|
||||||
@@ -26,6 +25,7 @@ from mcu import MCU, MCU_trsync
|
|||||||
from clocksync import SecondarySync
|
from clocksync import SecondarySync
|
||||||
|
|
||||||
STREAM_BUFFER_LIMIT_DEFAULT = 100
|
STREAM_BUFFER_LIMIT_DEFAULT = 100
|
||||||
|
STREAM_TIMEOUT = 1.0
|
||||||
|
|
||||||
class IDMProbe:
|
class IDMProbe:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
@@ -44,6 +44,7 @@ class IDMProbe:
|
|||||||
self.trigger_dive_threshold = config.getfloat(
|
self.trigger_dive_threshold = config.getfloat(
|
||||||
'trigger_dive_threshold', 1.)
|
'trigger_dive_threshold', 1.)
|
||||||
self.trigger_hysteresis = config.getfloat('trigger_hysteresis', 0.006)
|
self.trigger_hysteresis = config.getfloat('trigger_hysteresis', 0.006)
|
||||||
|
self.z_settling_time = config.getint("z_settling_time", 5, minval=0)
|
||||||
|
|
||||||
# If using paper for calibration, this would be .1mm
|
# If using paper for calibration, this would be .1mm
|
||||||
self.cal_nozzle_z = config.getfloat('cal_nozzle_z', 0.1)
|
self.cal_nozzle_z = config.getfloat('cal_nozzle_z', 0.1)
|
||||||
@@ -57,6 +58,7 @@ class IDMProbe:
|
|||||||
self.models = {}
|
self.models = {}
|
||||||
self.model_temp_builder = IDMTempModelBuilder.load(config)
|
self.model_temp_builder = IDMTempModelBuilder.load(config)
|
||||||
self.model_temp = None
|
self.model_temp = None
|
||||||
|
self.fmin = None
|
||||||
self.default_model_name = config.get('default_model_name', 'default')
|
self.default_model_name = config.get('default_model_name', 'default')
|
||||||
self.model_manager = ModelManager(self)
|
self.model_manager = ModelManager(self)
|
||||||
|
|
||||||
@@ -66,10 +68,13 @@ class IDMProbe:
|
|||||||
self.measured_max = 0.
|
self.measured_max = 0.
|
||||||
|
|
||||||
self.last_sample = None
|
self.last_sample = None
|
||||||
|
self.hardware_failure = None
|
||||||
|
|
||||||
self.mesh_helper = IDMMeshHelper.create(self, config)
|
self.mesh_helper = IDMMeshHelper.create(self, config)
|
||||||
|
|
||||||
self._stream_en = 0
|
self._stream_en = 0
|
||||||
|
self._stream_timeout_timer = self.reactor.register_timer(
|
||||||
|
self._stream_timeout)
|
||||||
self._stream_callbacks = {}
|
self._stream_callbacks = {}
|
||||||
self._stream_latency_requests = {}
|
self._stream_latency_requests = {}
|
||||||
self._stream_buffer = []
|
self._stream_buffer = []
|
||||||
@@ -133,6 +138,8 @@ class IDMProbe:
|
|||||||
self.idm_stream_cmd.send([0])
|
self.idm_stream_cmd.send([0])
|
||||||
|
|
||||||
self.model_temp = self.model_temp_builder.build_with_base(self)
|
self.model_temp = self.model_temp_builder.build_with_base(self)
|
||||||
|
if self.model_temp:
|
||||||
|
self.fmin = self.model_temp.fmin
|
||||||
self.model = self.models.get(self.default_model_name, None)
|
self.model = self.models.get(self.default_model_name, None)
|
||||||
if self.model:
|
if self.model:
|
||||||
self._apply_threshold()
|
self._apply_threshold()
|
||||||
@@ -141,6 +148,7 @@ class IDMProbe:
|
|||||||
constants = self._mcu.get_constants()
|
constants = self._mcu.get_constants()
|
||||||
|
|
||||||
self.sensor_freq = self._mcu._mcu_freq if self._mcu._mcu_freq < 20000000 else self._mcu._mcu_freq/2
|
self.sensor_freq = self._mcu._mcu_freq if self._mcu._mcu_freq < 20000000 else self._mcu._mcu_freq/2
|
||||||
|
|
||||||
self.inv_adc_max = 1.0 / constants.get("ADC_MAX")
|
self.inv_adc_max = 1.0 / constants.get("ADC_MAX")
|
||||||
self.temp_smooth_count = constants.get('IDM_ADC_SMOOTH_COUNT')
|
self.temp_smooth_count = constants.get('IDM_ADC_SMOOTH_COUNT')
|
||||||
self.thermistor = thermistor.Thermistor(10000., 0.)
|
self.thermistor = thermistor.Thermistor(10000., 0.)
|
||||||
@@ -197,6 +205,7 @@ class IDMProbe:
|
|||||||
raise self.printer.command_error("No IDM model loaded")
|
raise self.printer.command_error("No IDM model loaded")
|
||||||
|
|
||||||
speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.)
|
speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.)
|
||||||
|
allow_faulty = gcmd.get_int('ALLOW_FAULTY_COORDINATE', 0) != 0
|
||||||
lift_speed = self.get_lift_speed(gcmd)
|
lift_speed = self.get_lift_speed(gcmd)
|
||||||
toolhead = self.printer.lookup_object('toolhead')
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
curtime = self.reactor.monotonic()
|
curtime = self.reactor.monotonic()
|
||||||
@@ -205,7 +214,7 @@ class IDMProbe:
|
|||||||
|
|
||||||
self._start_streaming()
|
self._start_streaming()
|
||||||
try:
|
try:
|
||||||
return self._probe(speed)
|
return self._probe(speed, allow_faulty=allow_faulty)
|
||||||
finally:
|
finally:
|
||||||
self._stop_streaming()
|
self._stop_streaming()
|
||||||
|
|
||||||
@@ -225,32 +234,40 @@ class IDMProbe:
|
|||||||
pos[2] = status['axis_minimum'][2]
|
pos[2] = status['axis_minimum'][2]
|
||||||
try:
|
try:
|
||||||
self.phoming.probing_move(self.mcu_probe, pos, speed)
|
self.phoming.probing_move(self.mcu_probe, pos, speed)
|
||||||
samples = self._sample_printtime_sync(50)
|
self._sample_printtime_sync(self.z_settling_time)
|
||||||
except self.printer.command_error as e:
|
except self.printer.command_error as e:
|
||||||
reason = str(e)
|
reason = str(e)
|
||||||
if "Timeout during probing move" in reason:
|
if "Timeout during probing move" in reason:
|
||||||
reason += probe.HINT_TIMEOUT
|
reason += probe.HINT_TIMEOUT
|
||||||
raise self.printer.command_error(reason)
|
raise self.printer.command_error(reason)
|
||||||
|
|
||||||
def _sample(self, num_samples):
|
def _probe(self, speed, num_samples=10, allow_faulty=False):
|
||||||
samples = self._sample_printtime_sync(5, num_samples)
|
|
||||||
return (median([s['dist'] for s in samples]), samples)
|
|
||||||
|
|
||||||
def _probe(self, speed, num_samples=10):
|
|
||||||
target = self.trigger_distance
|
target = self.trigger_distance
|
||||||
tdt = self.trigger_dive_threshold
|
tdt = self.trigger_dive_threshold
|
||||||
(dist, samples) = self._sample(num_samples)
|
(dist, samples) = self._sample(5, num_samples)
|
||||||
|
|
||||||
|
x, y = samples[0]['pos'][0:2]
|
||||||
|
if self._is_faulty_coordinate(x, y, True):
|
||||||
|
msg = "Probing within a faulty area"
|
||||||
|
if not allow_faulty:
|
||||||
|
raise self.printer.command_error(msg)
|
||||||
|
else:
|
||||||
|
self.gcode.respond_raw("!! " + msg + "\n")
|
||||||
|
|
||||||
if dist > target + tdt:
|
if dist > target + tdt:
|
||||||
# If we are above the dive threshold right now, we'll need to
|
# If we are above the dive threshold right now, we'll need to
|
||||||
# do probing move and then re-measure
|
# do probing move and then re-measure
|
||||||
self._probing_move_to_probing_height(speed)
|
self._probing_move_to_probing_height(speed)
|
||||||
(dist, samples) = self._sample(num_samples)
|
(dist, samples) = self._sample(self.z_settling_time, num_samples)
|
||||||
|
elif math.isinf(dist) and dist < 0:
|
||||||
|
# We were below the valid range of the model
|
||||||
|
msg = "Attempted to probe with Beacon below calibrated model range"
|
||||||
|
raise self.printer.command_error(msg)
|
||||||
elif self.toolhead.get_position()[2] < target - tdt:
|
elif self.toolhead.get_position()[2] < target - tdt:
|
||||||
# We are below the probing target height, we'll move to the
|
# We are below the probing target height, we'll move to the
|
||||||
# correct height and take a new sample.
|
# correct height and take a new sample.
|
||||||
self._move_to_probing_height(speed)
|
self._move_to_probing_height(speed)
|
||||||
(dist, samples) = self._sample(num_samples)
|
(dist, samples) = self._sample(self.z_settling_time, num_samples)
|
||||||
|
|
||||||
pos = samples[0]['pos']
|
pos = samples[0]['pos']
|
||||||
|
|
||||||
@@ -262,11 +279,19 @@ class IDMProbe:
|
|||||||
# Calibration routines
|
# Calibration routines
|
||||||
|
|
||||||
def _start_calibration(self, gcmd):
|
def _start_calibration(self, gcmd):
|
||||||
|
|
||||||
|
allow_faulty = gcmd.get_int('ALLOW_FAULTY_COORDINATE', 0) != 0
|
||||||
if gcmd.get("SKIP_MANUAL_PROBE", None) is not None:
|
if gcmd.get("SKIP_MANUAL_PROBE", None) is not None:
|
||||||
kin = self.toolhead.get_kinematics()
|
kin = self.toolhead.get_kinematics()
|
||||||
kin_spos = {s.get_name(): s.get_commanded_position()
|
kin_spos = {s.get_name(): s.get_commanded_position()
|
||||||
for s in kin.get_steppers()}
|
for s in kin.get_steppers()}
|
||||||
kin_pos = kin.calc_position(kin_spos)
|
kin_pos = kin.calc_position(kin_spos)
|
||||||
|
if self._is_faulty_coordinate(kin_pos[0], kin_pos[1]):
|
||||||
|
msg = "Calibrating within a faulty area"
|
||||||
|
if not allow_faulty:
|
||||||
|
raise gcmd.error(msg)
|
||||||
|
else:
|
||||||
|
gcmd.respond_raw("!! " + msg + "\n")
|
||||||
self._calibrate(gcmd, kin_pos, False)
|
self._calibrate(gcmd, kin_pos, False)
|
||||||
else:
|
else:
|
||||||
curtime = self.printer.get_reactor().monotonic()
|
curtime = self.printer.get_reactor().monotonic()
|
||||||
@@ -275,6 +300,14 @@ class IDMProbe:
|
|||||||
raise self.printer.command_error("Must home X and Y "
|
raise self.printer.command_error("Must home X and Y "
|
||||||
"before calibration")
|
"before calibration")
|
||||||
|
|
||||||
|
kin_pos = self.toolhead.get_position()
|
||||||
|
if self._is_faulty_coordinate(kin_pos[0], kin_pos[1]):
|
||||||
|
msg = "Calibrating within a faulty area"
|
||||||
|
if not allow_faulty:
|
||||||
|
raise gcmd.error(msg)
|
||||||
|
else:
|
||||||
|
gcmd.respond_raw("!! " + msg + "\n")
|
||||||
|
|
||||||
forced_z = False
|
forced_z = False
|
||||||
if 'z' not in kin_status['homed_axes']:
|
if 'z' not in kin_status['homed_axes']:
|
||||||
self.toolhead.get_last_move_time()
|
self.toolhead.get_last_move_time()
|
||||||
@@ -309,6 +342,7 @@ class IDMProbe:
|
|||||||
|
|
||||||
# Move over to probe coordinate and pull out backlash
|
# Move over to probe coordinate and pull out backlash
|
||||||
curpos = self.toolhead.get_position()
|
curpos = self.toolhead.get_position()
|
||||||
|
|
||||||
curpos[2] = cal_max_z + self.backlash_comp
|
curpos[2] = cal_max_z + self.backlash_comp
|
||||||
toolhead.manual_move(curpos, move_speed) # Up
|
toolhead.manual_move(curpos, move_speed) # Up
|
||||||
curpos[0] -= self.x_offset
|
curpos[0] -= self.x_offset
|
||||||
@@ -386,8 +420,31 @@ class IDMProbe:
|
|||||||
"name '%s'" % (name,))
|
"name '%s'" % (name,))
|
||||||
self.models[name] = model
|
self.models[name] = model
|
||||||
|
|
||||||
|
def _is_faulty_coordinate(self, x, y, add_offsets=False):
|
||||||
|
if not self.mesh_helper:
|
||||||
|
return False
|
||||||
|
return self.mesh_helper._is_faulty_coordinate(x, y, add_offsets)
|
||||||
|
|
||||||
# Streaming mode
|
# Streaming mode
|
||||||
|
|
||||||
|
def _check_hardware(self, sample):
|
||||||
|
if not self.hardware_failure:
|
||||||
|
msg = None
|
||||||
|
if sample['data'] == 0xFFFFFFF:
|
||||||
|
msg = "coil is shorted or not connected"
|
||||||
|
elif self.fmin is not None and sample['freq'] > 1.35 * self.fmin:
|
||||||
|
msg = "coil expected max frequency exceeded"
|
||||||
|
if msg:
|
||||||
|
msg = "IDM hardware issue: " + msg
|
||||||
|
self.hardware_failure = msg
|
||||||
|
logging.error(msg)
|
||||||
|
if self._stream_en:
|
||||||
|
self.printer.invoke_shutdown(msg)
|
||||||
|
else:
|
||||||
|
self.gcode.respond_raw("!! " + msg + "\n")
|
||||||
|
elif self._stream_en:
|
||||||
|
self.printer.invoke_shutdown(self.hardware_failure)
|
||||||
|
|
||||||
def _enrich_sample_time(self, sample):
|
def _enrich_sample_time(self, sample):
|
||||||
clock = sample['clock'] = self._mcu.clock32_to_clock64(sample['clock'])
|
clock = sample['clock'] = self._mcu.clock32_to_clock64(sample['clock'])
|
||||||
sample['time'] = self._mcu.clock_to_print_time(clock)
|
sample['time'] = self._mcu.clock_to_print_time(clock)
|
||||||
@@ -396,10 +453,13 @@ class IDMProbe:
|
|||||||
temp_adc = sample['temp'] / self.temp_smooth_count * self.inv_adc_max
|
temp_adc = sample['temp'] / self.temp_smooth_count * self.inv_adc_max
|
||||||
sample['temp'] = self.thermistor.calc_temp(temp_adc)
|
sample['temp'] = self.thermistor.calc_temp(temp_adc)
|
||||||
|
|
||||||
def _enrich_sample(self, sample):
|
def _enrich_sample_freq(self, sample):
|
||||||
sample['data_smooth'] = self._data_filter.value()
|
sample['data_smooth'] = self._data_filter.value()
|
||||||
freq = sample['freq'] = self.count_to_freq(sample['data_smooth'])
|
sample['freq'] = self.count_to_freq(sample['data_smooth'])
|
||||||
sample['dist'] = self.freq_to_dist(freq, sample['temp'])
|
self._check_hardware(sample)
|
||||||
|
|
||||||
|
def _enrich_sample(self, sample):
|
||||||
|
sample['dist'] = self.freq_to_dist(sample['freq'], sample['temp'])
|
||||||
pos, vel = self._get_trapq_position(sample['time'])
|
pos, vel = self._get_trapq_position(sample['time'])
|
||||||
|
|
||||||
if pos is None:
|
if pos is None:
|
||||||
@@ -410,15 +470,28 @@ class IDMProbe:
|
|||||||
def _start_streaming(self):
|
def _start_streaming(self):
|
||||||
if self._stream_en == 0:
|
if self._stream_en == 0:
|
||||||
self.idm_stream_cmd.send([1])
|
self.idm_stream_cmd.send([1])
|
||||||
|
curtime = self.reactor.monotonic()
|
||||||
|
self.reactor.update_timer(self._stream_timeout_timer,
|
||||||
|
curtime + STREAM_TIMEOUT)
|
||||||
self._stream_en += 1
|
self._stream_en += 1
|
||||||
self._data_filter.reset()
|
self._data_filter.reset()
|
||||||
self._stream_flush()
|
self._stream_flush()
|
||||||
def _stop_streaming(self):
|
def _stop_streaming(self):
|
||||||
self._stream_en -= 1
|
self._stream_en -= 1
|
||||||
if self._stream_en == 0:
|
if self._stream_en == 0:
|
||||||
|
self.reactor.update_timer(self._stream_timeout_timer,
|
||||||
|
self.reactor.NEVER)
|
||||||
self.idm_stream_cmd.send([0])
|
self.idm_stream_cmd.send([0])
|
||||||
self._stream_flush()
|
self._stream_flush()
|
||||||
|
|
||||||
|
def _stream_timeout(self, eventtime):
|
||||||
|
if not self._stream_en:
|
||||||
|
return self.reactor.NEVER
|
||||||
|
msg = "Beacon sensor not receiving data"
|
||||||
|
logging.error(msg)
|
||||||
|
self.printer.invoke_shutdown(msg)
|
||||||
|
return self.reactor.NEVER
|
||||||
|
|
||||||
def request_stream_latency(self, latency):
|
def request_stream_latency(self, latency):
|
||||||
next_key = 0
|
next_key = 0
|
||||||
if self._stream_latency_requests:
|
if self._stream_latency_requests:
|
||||||
@@ -452,10 +525,23 @@ class IDMProbe:
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
samples = self._stream_samples_queue.get_nowait()
|
samples = self._stream_samples_queue.get_nowait()
|
||||||
|
updated_timer = False
|
||||||
for sample in samples:
|
for sample in samples:
|
||||||
self._enrich_sample_temp(sample)
|
if not updated_timer:
|
||||||
|
curtime = self.reactor.monotonic()
|
||||||
|
self.reactor.update_timer(self._stream_timeout_timer,
|
||||||
|
curtime + STREAM_TIMEOUT)
|
||||||
|
updated_timer = True
|
||||||
|
|
||||||
|
self._enrich_sample_temp(sample)
|
||||||
temp = sample['temp']
|
temp = sample['temp']
|
||||||
|
if self.model_temp is not None and not (-40 < temp < 180):
|
||||||
|
msg = ("IDM temperature sensor faulty(read %.2f C),"
|
||||||
|
" disabling temperaure compensation" % (temp,))
|
||||||
|
logging.error(msg)
|
||||||
|
self.gcode.respond_raw("!! " + msg + "\n")
|
||||||
|
self.model_temp = None
|
||||||
|
|
||||||
self.last_temp = temp
|
self.last_temp = temp
|
||||||
if temp:
|
if temp:
|
||||||
self.measured_min = min(self.measured_min, temp)
|
self.measured_min = min(self.measured_min, temp)
|
||||||
@@ -463,10 +549,11 @@ class IDMProbe:
|
|||||||
|
|
||||||
self._enrich_sample_time(sample)
|
self._enrich_sample_time(sample)
|
||||||
self._data_filter.update(sample['time'], sample['data'])
|
self._data_filter.update(sample['time'], sample['data'])
|
||||||
|
self._enrich_sample_freq(sample)
|
||||||
|
|
||||||
if len(self._stream_callbacks) > 0:
|
if len(self._stream_callbacks) > 0:
|
||||||
self._enrich_sample(sample)
|
self._enrich_sample(sample)
|
||||||
for cb in self._stream_callbacks.values():
|
for cb in list(self._stream_callbacks.values()):
|
||||||
cb(sample)
|
cb(sample)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
return
|
return
|
||||||
@@ -529,6 +616,10 @@ class IDMProbe:
|
|||||||
else:
|
else:
|
||||||
return samples
|
return samples
|
||||||
|
|
||||||
|
def _sample(self, skip, count):
|
||||||
|
samples = self._sample_printtime_sync(skip, count)
|
||||||
|
return (median([s['dist'] for s in samples]), samples)
|
||||||
|
|
||||||
def _sample_async(self, count=1):
|
def _sample_async(self, count=1):
|
||||||
samples = []
|
samples = []
|
||||||
def cb(sample):
|
def cb(sample):
|
||||||
@@ -538,6 +629,7 @@ class IDMProbe:
|
|||||||
|
|
||||||
with self.streaming_session(cb, latency=count) as ss:
|
with self.streaming_session(cb, latency=count) as ss:
|
||||||
ss.wait()
|
ss.wait()
|
||||||
|
|
||||||
if count == 1:
|
if count == 1:
|
||||||
return samples[0]
|
return samples[0]
|
||||||
else:
|
else:
|
||||||
@@ -560,9 +652,14 @@ class IDMProbe:
|
|||||||
return self.model.freq_to_dist(freq, temp)
|
return self.model.freq_to_dist(freq, temp)
|
||||||
|
|
||||||
def get_status(self, eventtime):
|
def get_status(self, eventtime):
|
||||||
|
model = None
|
||||||
|
if self.model is not None:
|
||||||
|
model = self.model.name
|
||||||
return {
|
return {
|
||||||
'last_sample': self.last_sample
|
'last_sample': self.last_sample,
|
||||||
|
'model': model,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Webhook handlers
|
# Webhook handlers
|
||||||
|
|
||||||
def _handle_req_status(self, web_request):
|
def _handle_req_status(self, web_request):
|
||||||
@@ -604,6 +701,7 @@ class IDMProbe:
|
|||||||
target = gcmd.get_float('Z', self.trigger_distance)
|
target = gcmd.get_float('Z', self.trigger_distance)
|
||||||
|
|
||||||
num_samples = gcmd.get_int('SAMPLES', 20)
|
num_samples = gcmd.get_int('SAMPLES', 20)
|
||||||
|
wait = self.z_settling_time
|
||||||
|
|
||||||
samples_up = []
|
samples_up = []
|
||||||
samples_down = []
|
samples_down = []
|
||||||
@@ -613,7 +711,7 @@ class IDMProbe:
|
|||||||
try:
|
try:
|
||||||
self._start_streaming()
|
self._start_streaming()
|
||||||
|
|
||||||
(cur_dist, _samples) = self._sample(10)
|
(cur_dist, _samples) = self._sample(wait, 10)
|
||||||
pos = self.toolhead.get_position()
|
pos = self.toolhead.get_position()
|
||||||
missing = target - cur_dist
|
missing = target - cur_dist
|
||||||
target = pos[2] + missing
|
target = pos[2] + missing
|
||||||
@@ -628,7 +726,7 @@ class IDMProbe:
|
|||||||
liftpos = [None, None, target]
|
liftpos = [None, None, target]
|
||||||
self.toolhead.manual_move(liftpos, lift_speed)
|
self.toolhead.manual_move(liftpos, lift_speed)
|
||||||
self.toolhead.wait_moves()
|
self.toolhead.wait_moves()
|
||||||
(dist, _samples) = self._sample(10)
|
(dist, _samples) = self._sample(wait, 10)
|
||||||
{-1: samples_up, 1: samples_down}[next_dir].append(dist)
|
{-1: samples_up, 1: samples_down}[next_dir].append(dist)
|
||||||
next_dir = next_dir * -1
|
next_dir = next_dir * -1
|
||||||
|
|
||||||
@@ -703,6 +801,7 @@ class IDMProbe:
|
|||||||
lift_speed = self.get_lift_speed(gcmd)
|
lift_speed = self.get_lift_speed(gcmd)
|
||||||
sample_count = gcmd.get_int("SAMPLES", 10, minval=1)
|
sample_count = gcmd.get_int("SAMPLES", 10, minval=1)
|
||||||
sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", 0)
|
sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", 0)
|
||||||
|
allow_faulty = gcmd.get_int('ALLOW_FAULTY_COORDINATE', 0) != 0
|
||||||
pos = self.toolhead.get_position()
|
pos = self.toolhead.get_position()
|
||||||
gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f"
|
gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f"
|
||||||
" (samples=%d retract=%.3f"
|
" (samples=%d retract=%.3f"
|
||||||
@@ -718,7 +817,7 @@ class IDMProbe:
|
|||||||
self.multi_probe_begin()
|
self.multi_probe_begin()
|
||||||
positions = []
|
positions = []
|
||||||
while len(positions) < sample_count:
|
while len(positions) < sample_count:
|
||||||
pos = self._probe(speed)
|
pos = self._probe(speed, allow_faulty=allow_faulty)
|
||||||
positions.append(pos)
|
positions.append(pos)
|
||||||
self.toolhead.manual_move(liftpos, lift_speed)
|
self.toolhead.manual_move(liftpos, lift_speed)
|
||||||
self.multi_probe_end()
|
self.multi_probe_end()
|
||||||
@@ -806,7 +905,14 @@ class IDMModel:
|
|||||||
"the printer." % (self.name,))
|
"the printer." % (self.name,))
|
||||||
|
|
||||||
def freq_to_dist_raw(self, freq):
|
def freq_to_dist_raw(self, freq):
|
||||||
return float(self.poly(1/freq) - self.offset)
|
[begin, end] = self.poly.domain
|
||||||
|
invfreq = 1/freq
|
||||||
|
if invfreq > end:
|
||||||
|
return float('inf')
|
||||||
|
elif invfreq < begin:
|
||||||
|
return float('-inf')
|
||||||
|
else:
|
||||||
|
return float(self.poly(invfreq) - self.offset)
|
||||||
|
|
||||||
def freq_to_dist(self, freq, temp):
|
def freq_to_dist(self, freq, temp):
|
||||||
if self.temp is not None and \
|
if self.temp is not None and \
|
||||||
@@ -816,6 +922,10 @@ class IDMModel:
|
|||||||
return self.freq_to_dist_raw(freq)
|
return self.freq_to_dist_raw(freq)
|
||||||
|
|
||||||
def dist_to_freq_raw(self, dist, max_e=0.00000001):
|
def dist_to_freq_raw(self, dist, max_e=0.00000001):
|
||||||
|
if dist < self.min_z or dist > self.max_z:
|
||||||
|
msg = ("Attempted to map out-of-range distance %f, valid range "
|
||||||
|
"[%.3f, %.3f]" % (dist, self.min_z, self.max_z))
|
||||||
|
raise self.idm.printer.command_error(msg)
|
||||||
dist += self.offset
|
dist += self.offset
|
||||||
[begin, end] = self.poly.domain
|
[begin, end] = self.poly.domain
|
||||||
for _ in range(0, 50):
|
for _ in range(0, 50):
|
||||||
@@ -1172,8 +1282,11 @@ class IDMEndstopWrapper:
|
|||||||
|
|
||||||
# After homing Z we perform a measurement and adjust the toolhead
|
# After homing Z we perform a measurement and adjust the toolhead
|
||||||
# kinematic position.
|
# kinematic position.
|
||||||
samples = self.idm._sample_printtime_sync(5, 10)
|
(dist, samples) = self.idm._sample(self.idm.z_settling_time, 10)
|
||||||
dist = median([s['dist'] for s in samples])
|
if math.isinf(dist):
|
||||||
|
logging.error("Post-homing adjustment measured samples %s", samples)
|
||||||
|
raise self.idm.printer.command_error(
|
||||||
|
"Toolhead stopped below model range")
|
||||||
homing_state.set_homed_position([None, None, dist])
|
homing_state.set_homed_position([None, None, dist])
|
||||||
|
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
@@ -1208,6 +1321,7 @@ class IDMEndstopWrapper:
|
|||||||
|
|
||||||
self.is_homing = True
|
self.is_homing = True
|
||||||
self.idm._apply_threshold()
|
self.idm._apply_threshold()
|
||||||
|
self.idm._sample_async()
|
||||||
clock = self._mcu.print_time_to_clock(print_time)
|
clock = self._mcu.print_time_to_clock(print_time)
|
||||||
rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock
|
rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock
|
||||||
self._rest_ticks = rest_ticks
|
self._rest_ticks = rest_ticks
|
||||||
@@ -1473,6 +1587,9 @@ class IDMMeshHelper:
|
|||||||
self.toolhead.manual_move([x, y, None], speed)
|
self.toolhead.manual_move([x, y, None], speed)
|
||||||
self.toolhead.wait_moves()
|
self.toolhead.wait_moves()
|
||||||
|
|
||||||
|
def _is_valid_position(self, x, y):
|
||||||
|
return self.min_x <= x <= self.max_x and self.min_y <= y <= self.min_y
|
||||||
|
|
||||||
def _sample_mesh(self, gcmd, path, speed, runs):
|
def _sample_mesh(self, gcmd, path, speed, runs):
|
||||||
cs = gcmd.get_float("CLUSTER_SIZE", self.cluster_size, minval=0.)
|
cs = gcmd.get_float("CLUSTER_SIZE", self.cluster_size, minval=0.)
|
||||||
|
|
||||||
@@ -1481,14 +1598,19 @@ class IDMMeshHelper:
|
|||||||
|
|
||||||
clusters = {}
|
clusters = {}
|
||||||
total_samples = [0]
|
total_samples = [0]
|
||||||
|
invalid_samples = [0]
|
||||||
|
|
||||||
def cb(sample):
|
def cb(sample):
|
||||||
total_samples[0] += 1
|
total_samples[0] += 1
|
||||||
|
d = sample['dist']
|
||||||
(x, y, z) = sample['pos']
|
(x, y, z) = sample['pos']
|
||||||
x += xo
|
x += xo
|
||||||
y += yo
|
y += yo
|
||||||
d = sample['dist']
|
|
||||||
|
if math.isinf(d):
|
||||||
|
if self._is_valid_position(x, y):
|
||||||
|
invalid_samples[0] += 1
|
||||||
|
return
|
||||||
|
|
||||||
# Calculate coordinate of the cluster we are in
|
# Calculate coordinate of the cluster we are in
|
||||||
xi = int(round((x - min_x) / self.step_x))
|
xi = int(round((x - min_x) / self.step_x))
|
||||||
@@ -1515,11 +1637,17 @@ class IDMMeshHelper:
|
|||||||
|
|
||||||
gcmd.respond_info("Sampled %d total points over %d runs" %
|
gcmd.respond_info("Sampled %d total points over %d runs" %
|
||||||
(total_samples[0], runs))
|
(total_samples[0], runs))
|
||||||
|
if invalid_samples[0]:
|
||||||
|
gcmd.respond_info("!! Encountered %d invalid samples!" % (invalid_samples[0],))
|
||||||
gcmd.respond_info("Samples binned in %d clusters" % (len(clusters),))
|
gcmd.respond_info("Samples binned in %d clusters" % (len(clusters),))
|
||||||
|
|
||||||
return clusters
|
return clusters
|
||||||
|
|
||||||
def _is_faulty_coordinate(self, x, y):
|
def _is_faulty_coordinate(self, x, y, add_offsets=False):
|
||||||
|
if add_offsets:
|
||||||
|
xo, yo = self.idm.x_offset, self.idm.y_offset
|
||||||
|
x += xo
|
||||||
|
y += yo
|
||||||
for r in self.faulty_regions:
|
for r in self.faulty_regions:
|
||||||
if r.is_point_within(x, y):
|
if r.is_point_within(x, y):
|
||||||
return True
|
return True
|
||||||
|
|||||||
Reference in New Issue
Block a user