diff options
Diffstat (limited to 'scripts/graph_mesh.py')
-rwxr-xr-x | scripts/graph_mesh.py | 133 |
1 files changed, 83 insertions, 50 deletions
diff --git a/scripts/graph_mesh.py b/scripts/graph_mesh.py index 3a331e5d..a62f9df2 100755 --- a/scripts/graph_mesh.py +++ b/scripts/graph_mesh.py @@ -20,14 +20,14 @@ import matplotlib.cm as cm import matplotlib.pyplot as plt import matplotlib.animation as ani -MESH_DUMP_REQUEST = json.dumps( - {"id": 1, "method": "bed_mesh/dump_mesh"} -) +MESH_DUMP_REQUEST = json.dumps({"id": 1, "method": "bed_mesh/dump_mesh"}) + def sock_error_exit(msg): sys.stderr.write(msg + "\n") sys.exit(-1) + def webhook_socket_create(uds_filename): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) while 1: @@ -45,6 +45,7 @@ def webhook_socket_create(uds_filename): print("Connected") return sock + def process_message(msg): try: resp = json.loads(msg) @@ -54,16 +55,14 @@ def process_message(msg): return None if "error" in resp: err = resp["error"].get("message", "Unknown") - sock_error_exit( - "Error requesting mesh dump: %s" % (err,) - ) + sock_error_exit("Error requesting mesh dump: %s" % (err,)) return resp["result"] def request_from_unixsocket(unix_sock_name): print("Connecting to Unix Socket File '%s'" % (unix_sock_name,)) whsock = webhook_socket_create(unix_sock_name) - whsock.settimeout(1.) + whsock.settimeout(1.0) # send mesh query whsock.send(MESH_DUMP_REQUEST.encode() + b"\x03") sock_data = b"" @@ -84,11 +83,12 @@ def request_from_unixsocket(unix_sock_name): result = process_message(msg) if result is not None: return result - time.sleep(.1) + time.sleep(0.1) finally: whsock.close() sock_error_exit("Mesh dump request timed out") + def request_from_websocket(url): print("Connecting to websocket url '%s'" % (url,)) try: @@ -101,15 +101,16 @@ def request_from_websocket(url): end_time = time.monotonic() + 20.0 while time.monotonic() < end_time: try: - msg = websocket.recv(10.) + msg = websocket.recv(10.0) except TimeoutError: continue result = process_message(msg) if result is not None: return result - time.sleep(.1) + time.sleep(0.1) sock_error_exit("Mesh dump request timed out") + def request_mesh_data(input_name): url_match = re.match(r"((?:https?)|(?:wss?))://(.+)", input_name.lower()) if url_match is None: @@ -129,16 +130,21 @@ def request_mesh_data(input_name): url = "%s://%s/klippysocket" % (scheme, host) return request_from_websocket(url) + class PathAnimation: instance = None + def __init__(self, artist, x_travel, y_travel): self.travel_artist = artist self.x_travel = x_travel self.y_travel = y_travel fig = plt.gcf() self.animation = ani.FuncAnimation( - fig=fig, func=self.update, frames=self.gen_path_position(), - cache_frame_data=False, interval=60 + fig=fig, + func=self.update, + frames=self.gen_path_position(), + cache_frame_data=False, + interval=60, ) PathAnimation.instance = self @@ -164,6 +170,7 @@ def _gen_mesh_coords(min_c, max_c, count): dist = (max_c - min_c) / (count - 1) return [min_c + i * dist for i in range(count)] + def _plot_path(travel_path, probed, diff, cmd_args): x_travel, y_travel = np.array(travel_path).transpose() x_probed, y_probed = np.array(probed).transpose() @@ -183,6 +190,7 @@ def _plot_path(travel_path, probed, diff, cmd_args): if cmd_args.animate and cmd_args.output is None: PathAnimation(travel_line, x_travel, y_travel) + def _format_mesh_data(matrix, params): min_pt = (params["min_x"], params["min_y"]) max_pt = (params["max_x"], params["max_y"]) @@ -192,6 +200,7 @@ def _format_mesh_data(matrix, params): z = np.array(matrix) return x, y, z + def _set_xy_limits(mesh_data, cmd_args): if not cmd_args.scale_plot: return @@ -201,12 +210,14 @@ def _set_xy_limits(mesh_data, cmd_args): ax.set_xlim((axis_min[0], axis_max[0])) ax.set_ylim((axis_min[1], axis_max[1])) + def _plot_mesh(ax, matrix, params, cmap=cm.viridis, label=None): x, y, z = _format_mesh_data(matrix, params) surface = ax.plot_surface(x, y, z, cmap=cmap, label=label) scale = max(abs(z.min()), abs(z.max())) * 3 return surface, scale + def plot_probe_points(mesh_data, cmd_args): """Plot original generated points""" calibration = mesh_data["calibration"] @@ -217,6 +228,7 @@ def plot_probe_points(mesh_data, cmd_args): plt.plot(x, y, "b.") _set_xy_limits(mesh_data, cmd_args) + def plot_probe_path(mesh_data, cmd_args): """Plot probe travel path""" calibration = mesh_data["calibration"] @@ -227,6 +239,7 @@ def plot_probe_path(mesh_data, cmd_args): _plot_path(path_pts, path_pts[1:-1], diff, cmd_args) _set_xy_limits(mesh_data, cmd_args) + def plot_rapid_path(mesh_data, cmd_args): """Plot rapid scan travel path""" calibration = mesh_data["calibration"] @@ -239,6 +252,7 @@ def plot_rapid_path(mesh_data, cmd_args): _plot_path(rapid_path, probed, diff, cmd_args) _set_xy_limits(mesh_data, cmd_args) + def plot_probed_matrix(mesh_data, cmd_args): """Plot probed Z values""" ax = plt.subplot(projection="3d") @@ -259,9 +273,10 @@ def plot_probed_matrix(mesh_data, cmd_args): surface, scale = _plot_mesh(ax, matrix, params) ax.set_title("Probed Mesh (%s)" % (name,)) ax.set(zlim=(-scale, scale)) - plt.gcf().colorbar(surface, shrink=.75) + plt.gcf().colorbar(surface, shrink=0.75) _set_xy_limits(mesh_data, cmd_args) + def plot_mesh_matrix(mesh_data, cmd_args): """Plot mesh Z values""" ax = plt.subplot(projection="3d") @@ -274,9 +289,10 @@ def plot_mesh_matrix(mesh_data, cmd_args): name = req_mesh["name"] ax.set_title("Interpolated Mesh (%s)" % (name,)) ax.set(zlim=(-scale, scale)) - plt.gcf().colorbar(surface, shrink=.75) + plt.gcf().colorbar(surface, shrink=0.75) _set_xy_limits(mesh_data, cmd_args) + def plot_overlay(mesh_data, cmd_args): """Plots the current probed mesh overlaid with a profile""" ax = plt.subplot(projection="3d") @@ -301,10 +317,11 @@ def plot_overlay(mesh_data, cmd_args): ax.set_title("Probed Mesh Overlay") scale = max(cur_scale, prof_scale) ax.set(zlim=(-scale, scale)) - ax.legend(loc='best') - plt.gcf().colorbar(prof_surf, shrink=.75) + ax.legend(loc="best") + plt.gcf().colorbar(prof_surf, shrink=0.75) _set_xy_limits(mesh_data, cmd_args) + def plot_delta(mesh_data, cmd_args): """Plots the delta between current probed mesh and a profile""" ax = plt.subplot(projection="3d") @@ -327,9 +344,7 @@ def plot_delta(mesh_data, cmd_args): pfields = ("x_count", "y_count", "min_x", "max_x", "min_y", "max_y") for field in pfields: if abs(prof_params[field] - cur_params[field]) >= 1e-6: - raise Exception( - "Values for field %s do not match, cant plot deviation" - ) + raise Exception("Values for field %s do not match, cant plot deviation") delta = np.array(cur_matrix) - np.array(prof_matix) surface, scale = _plot_mesh(ax, delta, cur_params) ax.set(zlim=(-scale, scale)) @@ -347,12 +362,12 @@ PLOT_TYPES = { "delta": plot_delta, } + def print_types(cmd_args): - typelist = [ - "%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items() - ] + typelist = ["%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items()] print("\n".join(typelist)) + def plot_mesh_data(cmd_args): mesh_data = request_mesh_data(cmd_args.input) if cmd_args.output is not None: @@ -368,6 +383,7 @@ def plot_mesh_data(cmd_args): else: fig.savefig(cmd_args.output) + def _check_path_unique(name, path): path = np.array(path) unique_pts, counts = np.unique(path, return_counts=True, axis=0) @@ -380,6 +396,7 @@ def _check_path_unique(name, path): % (name, coord) ) + def _analyze_mesh(name, mesh_axes): print("\nAnalyzing Probed Mesh %s..." % (name,)) x, y, z = mesh_axes @@ -389,8 +406,8 @@ def _analyze_mesh(name, mesh_axes): print( " Min Coord (%.2f, %.2f), Max Coord (%.2f, %.2f), " - "Probe Count: (%d, %d)" % - (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0])) + "Probe Count: (%d, %d)" + % (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0])) ) print( " Mesh range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)" @@ -398,6 +415,7 @@ def _analyze_mesh(name, mesh_axes): ) print(" Mean: %.4f, Standard Deviation: %.4f" % (z.mean(), z.std())) + def _compare_mesh(name_a, name_b, mesh_a, mesh_b): ax, ay, az = mesh_a bx, by, bz = mesh_b @@ -414,10 +432,21 @@ def _compare_mesh(name_a, name_b, mesh_a, mesh_b): " Range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)\n" " Mean: %.6f, Standard Deviation: %.6f\n" " Absolute Max: %.6f, Absolute Mean: %.6f" - % (delta.min(), min_x, min_y, delta.max(), max_x, max_y, - delta.mean(), delta.std(), abs_max, abs_mean) + % ( + delta.min(), + min_x, + min_y, + delta.max(), + max_x, + max_y, + delta.mean(), + delta.std(), + abs_max, + abs_mean, + ) ) + def analyze(cmd_args): mesh_data = request_mesh_data(cmd_args.input) print("Analyzing Travel Path...") @@ -461,6 +490,7 @@ def analyze(cmd_args): for prof_name, prof_axes in formatted_data.items(): _compare_mesh(name, prof_name, current_axes, prof_axes) + def dump_request(cmd_args): mesh_data = request_mesh_data(cmd_args.input) outfile = cmd_args.output @@ -472,57 +502,60 @@ def dump_request(cmd_args): with open(outfile, "w") as f: f.write(json.dumps(mesh_data)) + def main(): parser = argparse.ArgumentParser(description="Graph Bed Mesh Data") sub_parsers = parser.add_subparsers() - list_parser = sub_parsers.add_parser( - "list", help="List available plot types" - ) + list_parser = sub_parsers.add_parser("list", help="List available plot types") list_parser.set_defaults(func=print_types) plot_parser = sub_parsers.add_parser("plot", help="Plot a specified type") analyze_parser = sub_parsers.add_parser( "analyze", help="Perform analysis on mesh data" ) - dump_parser = sub_parsers.add_parser( - "dump", help="Dump API response to json file" - ) + dump_parser = sub_parsers.add_parser("dump", help="Dump API response to json file") plot_parser.add_argument( - "-a", "--animate", action="store_true", - help="Animate paths in live preview" + "-a", "--animate", action="store_true", help="Animate paths in live preview" ) plot_parser.add_argument( - "-s", "--scale-plot", action="store_true", - help="Use axis limits reported by Klipper to scale plot X/Y" + "-s", + "--scale-plot", + action="store_true", + help="Use axis limits reported by Klipper to scale plot X/Y", ) plot_parser.add_argument( - "-p", "--profile-name", type=str, default=None, - help="Optional name of a profile to plot for 'probedz'" + "-p", + "--profile-name", + type=str, + default=None, + help="Optional name of a profile to plot for 'probedz'", ) plot_parser.add_argument( - "-o", "--output", type=str, default=None, - help="Output file path" + "-o", "--output", type=str, default=None, help="Output file path" ) plot_parser.add_argument( - "type", metavar="<plot type>", type=str, choices=PLOT_TYPES.keys(), - help="Type of data to graph" + "type", + metavar="<plot type>", + type=str, + choices=PLOT_TYPES.keys(), + help="Type of data to graph", ) plot_parser.add_argument( - "input", metavar="<input>", - help="Path/url to Klipper Socket or path to json file" + "input", + metavar="<input>", + help="Path/url to Klipper Socket or path to json file", ) plot_parser.set_defaults(func=plot_mesh_data) analyze_parser.add_argument( - "input", metavar="<input>", - help="Path/url to Klipper Socket or path to json file" + "input", + metavar="<input>", + help="Path/url to Klipper Socket or path to json file", ) analyze_parser.set_defaults(func=analyze) dump_parser.add_argument( - "-o", "--output", type=str, default=None, - help="Json output file path" + "-o", "--output", type=str, default=None, help="Json output file path" ) dump_parser.add_argument( - "input", metavar="<input>", - help="Path or url to Klipper Socket" + "input", metavar="<input>", help="Path or url to Klipper Socket" ) dump_parser.set_defaults(func=dump_request) cmd_args = parser.parse_args() |