From cdcc320710290673ed37c4106be8b910026f8dac Mon Sep 17 00:00:00 2001 From: Arksine Date: Sat, 26 Jan 2019 19:54:37 -0500 Subject: [PATCH] bed_mesh: add support for round beds Signed-off-by: Eric Callahan --- config/example-extras.cfg | 53 +++++++++++++---- klippy/extras/bed_mesh.py | 122 +++++++++++++++++++++++++++++--------- 2 files changed, 136 insertions(+), 39 deletions(-) diff --git a/config/example-extras.cfg b/config/example-extras.cfg index 2a2e8ccf..8cf5649c 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -117,6 +117,26 @@ # using a probe to home the z-axis, it is recommended to define # a [homing_override] section in printer.cfg to home toward the # center of the print area. +# +# Visual Examples: +# bed_shape = rectangular, probe_count = 3,3: +# x---x---x (max_point) +# | +# x---x---x +# | +# (min_point) x---x---x +# +# bed_shape = round, probe_count = 5, radius = r: +# x (0,r) end +# / +# x---x---x +# \ +# (-r,0) x---x---x---x---x (r,0) +# \ +# x---x---x +# / +# x (0,-r) start +# #[bed_mesh] #speed: 50 # The speed (in mm/s) of non-probing moves during the @@ -130,21 +150,34 @@ #sample_retract_dist: 2.0 # The distance (in mm) to retract between each sample if # sampling more than once. Default is 2mm. +#bed_shape: rectangular +# Defines the shape of the bed for probing. May be 'rectangular', +# as is common for cartesian printers, or 'round', as is common for +# delta printers. Default is rectangular. +#radius: +# Defines the radius to probe when the bed_shape is 'round'. Note +# that the radius is relative to the nozzle's origin, if using a +# probe be sure to account for its offset. This parameter must be +# provided if the bed_shape is 'round'. #min_point: -# An X,Y point defining the minimum coordinate to probe on -# the bed. Note that this refers to the nozzle position, -# and take care that you do not define a point that will move -# the probe off of the bed. This parameter must be provided. +# Defines the minimum x,y position to probe when the bed_shape +# is 'rectangular'. Note that this refers to the nozzle position, +# take care that you do not define a point that will move the +# probe off of the bed. This parameter must be provided. #max_point: -# An X,Y point defining the maximum coordinate to probe on -# the bed. Follow the same precautions as listed in min_point. +# Defines the maximum x,y position to probe when the bed_shape +# is 'rectangular'. Follow the same precautions as listed in min_point. # Also note that this does not necessarily define the last point # probed, only the maximum coordinate. This parameter must be provided. #probe_count: 3,3 -# A comma separated pair of integer values (X,Y) defining the number -# of points to probe along each axis. A single value is also valid, -# in which case that value will be for both axes. Default is 3,3 -# which probes a 3x3 grid. +## OR ## +#probe_count: 5 +# For 'rectangular' beds, this is a comma separate pair of integer +# values (X,Y) defining the number of points to probe along each axis. +# A single value is also valid, in which case that value will be applied +# to both axes. 'Round' beds only accept a single integer value that is +# applied to both axes. The probe count must be odd for round beds. +# Default is 3,3 for 'rectangular' beds, and 5 for 'round' beds. #fade_start: 1.0 # The gcode z position in which to start phasing out z-adjustment # when fade is enabled. Default is 1.0. diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index bcb16d45..f6322596 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -10,6 +10,8 @@ import json import probe import collections +BED_SHAPES = {'rectangular': 0, 'round': 1} + class BedMeshError(Exception): pass @@ -163,6 +165,7 @@ class BedMeshCalibrate: def __init__(self, config, bedmesh): self.printer = config.get_printer() self.name = config.get_name() + self.radius = None self.bedmesh = bedmesh self.probed_z_table = None self.build_map = False @@ -185,14 +188,29 @@ class BedMeshCalibrate: 'BED_MESH_PROFILE', self.cmd_BED_MESH_PROFILE, desc=self.cmd_BED_MESH_PROFILE_help) def _generate_points(self, config): - x_cnt, y_cnt = parse_pair( - config, ('probe_count', '3'), check=False, cast=int, minval=3) + shape = config.getchoice('bed_shape', BED_SHAPES, 'rectangular') + if shape == BED_SHAPES['round']: + x_cnt = y_cnt = config.getint('probe_count', 5) + # round beds must have an odd number of points along each axis + if not x_cnt & 1: + raise config.error( + "bed_mesh: probe_count must be odd for round beds") + self.radius = config.getfloat('radius', above=0.) + # radius may have precision to .1mm + self.radius = math.floor(self.radius * 10) / 10 + min_x = min_y = -self.radius + max_x = max_y = self.radius + else: + # rectangular + x_cnt, y_cnt = parse_pair( + config, ('probe_count', '3'), check=False, cast=int, minval=3) + min_x, min_y = parse_pair(config, ('min_point',)) + max_x, max_y = parse_pair(config, ('max_point',)) + if max_x <= min_x or max_y <= min_y: + raise config.error('bed_mesh: invalid min/max points') + self.probe_params['x_count'] = x_cnt self.probe_params['y_count'] = y_cnt - min_x, min_y = parse_pair(config, ('min_point',)) - max_x, max_y = parse_pair(config, ('max_point',)) - if max_x <= min_x or max_y <= min_y: - raise config.error('bed_mesh: invalid min/max points') x_dist = (max_x - min_x) / (x_cnt - 1) y_dist = (max_y - min_y) / (y_cnt - 1) # floor distances down to next hundredth @@ -200,8 +218,16 @@ class BedMeshCalibrate: y_dist = math.floor(y_dist * 100) / 100 if x_dist <= 1. or y_dist <= 1.: raise config.error("bed_mesh: min/max points too close together") - # re-calc x_max - max_x = min_x + x_dist * (x_cnt - 1) + + if self.radius is not None: + # round bed, min/max needs to be recalculated + y_dist = x_dist + new_r = (x_cnt / 2) * x_dist + min_x = min_y = -new_r + max_x = max_y = new_r + else: + # rectangular bed, only re-calc max_x + max_x = min_x + x_dist * (x_cnt - 1) pos_y = min_y points = [] for i in range(y_cnt): @@ -212,7 +238,14 @@ class BedMeshCalibrate: else: # move in negative direction pos_x = max_x - j * x_dist - points.append((pos_x, pos_y)) + if self.radius is None: + # rectangular bed, append + points.append((pos_x, pos_y)) + else: + # round bed, check distance from origin + dist_from_origin = math.sqrt(pos_x*pos_x + pos_y*pos_y) + if dist_from_origin <= self.radius: + points.append((pos_x, pos_y)) pos_y += y_dist logging.info('bed_mesh: generated points') for p in points: @@ -358,27 +391,58 @@ class BedMeshCalibrate: z_offset = offsets[2] x_cnt = self.probe_params['x_count'] y_cnt = self.probe_params['y_count'] - # create a 2-D array representing the probed z-positions. - self.probed_z_table = [ - [0. for i in range(x_cnt)] for j in range(y_cnt)] - # Check for multi-sampled points - z_table_len = x_cnt * y_cnt - if len(positions) != z_table_len: - raise self.gcode.error( - ("bed_mesh: Invalid probe table length:\n" - "Sampled table length: %d") % len(positions)) - # Populate the organized probed table - for i in range(z_table_len): - y_position = i / x_cnt - x_position = 0 - if y_position & 1 == 0: - # Even y count, x probed in positive directon - x_position = i % x_cnt + + self.probed_z_table = [] + row = [] + prev_pos = positions[0] + for pos in positions: + if not isclose(pos[1], prev_pos[1], abs_tol=.1): + # y has changed, append row and start new + self.probed_z_table.append(row) + row = [] + if pos[0] > prev_pos[0]: + # probed in the positive direction + row.append(pos[2] - z_offset) else: - # Odd y count, x probed in the negative directon - x_position = (x_cnt - 1) - (i % x_cnt) - self.probed_z_table[y_position][x_position] = \ - positions[i][2] - z_offset + # probed in the negative direction + row.insert(0, pos[2] - z_offset) + prev_pos = pos + # append last row + self.probed_z_table.append(row) + + # make sure the y-axis is the correct length + if len(self.probed_z_table) != y_cnt: + raise self.gcode.error( + ("bed_mesh: Invalid y-axis table length\n" + "Probed table length: %d Probed Table:\n%s") % + (len(self.probed_z_table), str(self.probed_z_table))) + + if self.radius is not None: + # round bed, extrapolate probed values to create a square mesh + for row in self.probed_z_table: + row_size = len(row) + if not row_size & 1: + # an even number of points in a row shouldn't be possible + msg = "bed_mesh: incorrect number of points sampled on X\n" + msg += "Probed Table:\n" + msg += str(self.probed_z_table) + raise self.gcode.error(msg) + buf_cnt = (x_cnt - row_size) / 2 + if buf_cnt == 0: + continue + left_buffer = [row[0]] * buf_cnt + right_buffer = [row[row_size-1]] * buf_cnt + row[0:0] = left_buffer + row.extend(right_buffer) + + # make sure that the x-axis is the correct length + for row in self.probed_z_table: + if len(row) != x_cnt: + raise self.gcode.error( + ("bed_mesh: invalid x-axis table length\n" + "Probed table length: %d Probed Table:\n%s") % + (len(self.probed_z_table), str(self.probed_z_table))) + if self.build_map: params = self.probe_params outdict = {