Klipper-delta_calibrate模块
配置信息
[delta_calibrate]
radius: 145
horizontal_move_z: 10 #This value is related to the lift height of the nozzle during delta_calibrate
Speed: 100#*# [delta_calibrate]
#*# height0 = -0.2
#*# height0_pos = 23052.500,23066.000,22937.000
#*# height1 = -0.2
#*# height1_pos = 27015.000,27090.000,20666.000
#*# height2 = -0.2
#*# height2_pos = 22532.500,29248.500,22422.500
#*# height3 = -0.2
#*# height3_pos = 20747.000,26212.500,26131.500
#*# height4 = -0.2
#*# height4_pos = 22366.000,22345.000,27131.500
#*# height5 = -0.2
#*# height5_pos = 25721.500,20936.500,25601.500
#*# height6 = -0.2
#*# height6_pos = 28191.500,22492.000,22328.000
- 服务启动时,加载配置对象时加载该模块并对其进行初始化处理。
入口
def load_config(config):return DeltaCalibrate(config)
初始化
class DeltaCalibrate:def __init__(self, config):self.printer = config.get_printer()self.printer.register_event_handler("klippy:connect",self.handle_connect)# Calculate default probing pointsradius = config.getfloat('radius', above=0.)points = [(0., 0.)]scatter = [.95, .90, .85, .70, .75, .80]for i in range(6):r = math.radians(90. + 60. * i)dist = radius * scatter[i]points.append((math.cos(r) * dist, math.sin(r) * dist))self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize, default_points=points)self.probe_helper.minimum_points(3)# Restore probe stable positionsself.last_probe_positions = []for i in range(999):height = config.getfloat("height%d" % (i,), None)if height is None:breakheight_pos = load_config_stable(config, "height%d_pos" % (i,))self.last_probe_positions.append((height, height_pos))# Restore manually entered heightsself.manual_heights = []for i in range(999):height = config.getfloat("manual_height%d" % (i,), None)if height is None:breakheight_pos = load_config_stable(config, "manual_height%d_pos"% (i,))self.manual_heights.append((height, height_pos))# Restore distance measurementsself.delta_analyze_entry = {'SCALE': (1.,)}self.last_distances = []for i in range(999):dist = config.getfloat("distance%d" % (i,), None)if dist is None:breakdistance_pos1 = load_config_stable(config, "distance%d_pos1" % (i,))distance_pos2 = load_config_stable(config, "distance%d_pos2" % (i,))self.last_distances.append((dist, distance_pos1, distance_pos2))# Register gcode commandsself.gcode = self.printer.lookup_object('gcode')self.gcode.register_command('DELTA_CALIBRATE', self.cmd_DELTA_CALIBRATE,desc=self.cmd_DELTA_CALIBRATE_help)self.gcode.register_command('DELTA_ANALYZE', self.cmd_DELTA_ANALYZE,desc=self.cmd_DELTA_ANALYZE_help)
-
初始化打印机与事件处理:
- 使用
config.get_printer()
方法获取打印机对象self.printer
。 - 注册事件处理程序,当事件
"klippy:connect"
触发时调用self.handle_connect
方法。
- 使用
-
计算默认探针点:
- 使用从配置中获取的
radius
参数,生成默认的探针点坐标。初始点(0., 0.)
加上基于scatter
数组的随机分布。 - 计算的点基于 60° 的角度分布,以生成六个均匀分布的探针点。
- 创建一个
ProbePointsHelper
对象self.probe_helper
,并将生成的默认探针点points
传递给它,同时设置最少探针点数为 3。
- 使用从配置中获取的
-
恢复探针的稳定位置:
- 从配置中加载每个
"height%d"
配置的高度和对应的稳定位置。 - 将每对 (height, position) 添加到
self.last_probe_positions
列表中。
- 从配置中加载每个
-
恢复手动输入的高度:
- 从配置中加载每个
"manual_height%d"
和对应的手动输入位置。 - 将其存储在
self.manual_heights
列表中。
- 从配置中加载每个
-
恢复距离测量:
- 使用键名
"distance%d"
来加载每个测量的距离值和两个位置坐标。 - 这些距离和位置对被保存在
self.last_distances
列表中。 self.delta_analyze_entry
是一个字典,包含默认的缩放值(1.,)
,可能用于进一步的分析或调整。
- 使用键名
-
注册 GCode 命令:
- 获取
gcode
对象并注册两条新的 GCode 命令: DELTA_CALIBRATE
:指向cmd_DELTA_CALIBRATE
方法。DELTA_ANALYZE
:指向cmd_DELTA_ANALYZE
方法。
- 获取
-
根据半径计算调平时探测的坐标点,该坐标点会在热床调平时使用。
- 初始化后的DeltaCalibrate对象信息。
delta 热床校准完成代码逻辑
def probe_finalize(self, offsets, positions):# Convert positions into (z_offset, stable_position) pairsz_offset = offsets[2]kin = self.printer.lookup_object('toolhead').get_kinematics()delta_params = kin.get_calibration()probe_positions = [(z_offset, delta_params.calc_stable_position(p))for p in positions]# Perform analysisself.calculate_params(probe_positions, self.last_distances)
def calculate_params(self, probe_positions, distances):height_positions = self.manual_heights + probe_positions# Setup for coordinate descent analysiskin = self.printer.lookup_object('toolhead').get_kinematics()orig_delta_params = odp = kin.get_calibration()adj_params, params = odp.coordinate_descent_params(distances)logging.info("Calculating delta_calibrate with:\n%s\n%s\n""Initial delta_calibrate parameters: %s",height_positions, distances, params)z_weight = 1.if distances:z_weight = len(distances) / (MEASURE_WEIGHT * len(probe_positions))# Perform coordinate descentdef delta_errorfunc(params):try:# Build new delta_params for params under testdelta_params = orig_delta_params.new_calibration(params)getpos = delta_params.get_position_from_stable# Calculate z height errorstotal_error = 0.for z_offset, stable_pos in height_positions:x, y, z = getpos(stable_pos)total_error += (z - z_offset)**2total_error *= z_weight# Calculate distance errorsfor dist, stable_pos1, stable_pos2 in distances:x1, y1, z1 = getpos(stable_pos1)x2, y2, z2 = getpos(stable_pos2)d = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)total_error += (d - dist)**2return total_errorexcept ValueError:return 9999999999999.9new_params = mathutil.background_coordinate_descent(self.printer, adj_params, params, delta_errorfunc)# Log and report resultslogging.info("Calculated delta_calibrate parameters: %s", new_params)new_delta_params = orig_delta_params.new_calibration(new_params)for z_offset, spos in height_positions:logging.info("height orig: %.6f new: %.6f goal: %.6f",orig_delta_params.get_position_from_stable(spos)[2],new_delta_params.get_position_from_stable(spos)[2],z_offset)for dist, spos1, spos2 in distances:x1, y1, z1 = orig_delta_params.get_position_from_stable(spos1)x2, y2, z2 = orig_delta_params.get_position_from_stable(spos2)orig_dist = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)x1, y1, z1 = new_delta_params.get_position_from_stable(spos1)x2, y2, z2 = new_delta_params.get_position_from_stable(spos2)new_dist = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)logging.info("distance orig: %.6f new: %.6f goal: %.6f",orig_dist, new_dist, dist)# Store results for SAVE_CONFIGself.save_state(probe_positions, distances, new_delta_params)self.gcode.respond_info("The SAVE_CONFIG command will update the printer config file\n""with these parameters and restart the printer.")
probe_finalize
- 输入:探针偏移 (
offsets
) 和探测到的位置信息 (positions
)。 - 操作:
- 将探测位置 (
positions
) 转换为稳定位置 (stable_position
) 和 Z 偏移对。 - 调用
calculate_params
方法,用探测数据和距离测量来进行参数计算。
- 将探测位置 (
- 输入:探针偏移 (
calculate_params
- 输入:探测结果 (
probe_positions
) 和距离测量数据 (distances
)。 - 操作:
- 准备探测和手动高度数据,用于后续分析。
- 初始化 kinematics(运动学)相关参数,并设置优化目标函数。
- 使用坐标下降法 (
coordinate_descent
) 来优化 Delta 几何参数。 - 打印计算结果并保存新校准参数。
- 输入:探测结果 (
坐标下降法
def background_coordinate_descent(printer, adj_params, params, error_func):parent_conn, child_conn = multiprocessing.Pipe()def wrapper():queuelogger.clear_bg_logging()try:res = coordinate_descent(adj_params, params, error_func)except:child_conn.send((True, traceback.format_exc()))child_conn.close()returnchild_conn.send((False, res))child_conn.close()# Start a process to perform the calculationcalc_proc = multiprocessing.Process(target=wrapper)calc_proc.daemon = Truecalc_proc.start()# Wait for the process to finishreactor = printer.get_reactor()gcode = printer.lookup_object("gcode")eventtime = last_report_time = reactor.monotonic()while calc_proc.is_alive():if eventtime > last_report_time + 5.:last_report_time = eventtimegcode.respond_info("Working on calibration...", log=False)eventtime = reactor.pause(eventtime + .1)# Return resultsis_err, res = parent_conn.recv()if is_err:raise Exception("Error in coordinate descent: %s" % (res,))calc_proc.join()parent_conn.close()return res
- 多进程初始化:
multiprocessing.Pipe
:- 创建父子连接,用于主进程与计算进程之间的数据通信。
wrapper
函数:- 包装实际的优化函数
coordinate_descent
,捕获异常并将结果通过child_conn
返回。
- 包装实际的优化函数
- 优化计算的子进程:
- 使用
multiprocessing.Process
创建后台进程运行wrapper
。 - 进程设置为守护进程(
daemon=True
),确保主程序退出时它会自动终止。
- 使用
- 主进程的状态监控与反馈:
- 监控进程状态:
- 每隔 5 秒发送一条用户友好的状态更新,提示校准过程正在进行。
- 使用
reactor.pause
延迟循环,避免占用 CPU 资源。
- 监控进程状态:
while calc_proc.is_alive():if eventtime > last_report_time + 5.:last_report_time = eventtimegcode.respond_info("Working on calibration...", log=False)eventtime = reactor.pause(eventtime + .1)
- 结果处理:
- 获取优化结果:从子进程接收数据。如果收到错误标志(
is_err=True
),抛出异常。
- 获取优化结果:从子进程接收数据。如果收到错误标志(
is_err, res = parent_conn.recv()
if is_err:raise Exception("Error in coordinate descent: %s" % (res,))
- 清理资源:
- 确保进程完全退出并关闭管道连接。
calc_proc.join()
parent_conn.close()
- 返回优化结果:
- 子进程正常结束后,将优化结果返回主进程继续使用。
def coordinate_descent(adj_params, params, error_func):# Define potential changesparams = dict(params)dp = {param_name: 1. for param_name in adj_params}# Calculate the errorbest_err = error_func(params)logging.info("Coordinate descent initial error: %s", best_err)threshold = 0.00001rounds = 0while sum(dp.values()) > threshold and rounds < 10000:rounds += 1for param_name in adj_params:orig = params[param_name]params[param_name] = orig + dp[param_name]err = error_func(params)if err < best_err:# There was some improvementbest_err = errdp[param_name] *= 1.1continueparams[param_name] = orig - dp[param_name]err = error_func(params)if err < best_err:# There was some improvementbest_err = errdp[param_name] *= 1.1continueparams[param_name] = origdp[param_name] *= 0.9logging.info("Coordinate descent best_err: %s rounds: %d",best_err, rounds)return params
- 多轮循环,达到坐标下降优化
def delta_errorfunc(params):try:# Build new delta_params for params under testdelta_params = orig_delta_params.new_calibration(params)getpos = delta_params.get_position_from_stable# Calculate z height errorstotal_error = 0.for z_offset, stable_pos in height_positions:x, y, z = getpos(stable_pos)total_error += (z - z_offset)**2total_error *= z_weight# Calculate distance errorsfor dist, stable_pos1, stable_pos2 in distances:x1, y1, z1 = getpos(stable_pos1)x2, y2, z2 = getpos(stable_pos2)d = math.sqrt((x1-x2)**2 + (y1-y2)**2 + (z1-z2)**2)total_error += (d - dist)**2return total_errorexcept ValueError:return 9999999999999.9
- 计算距离误差