"""Plot actual Bambu G-code extrusion paths, separating model and supports. python3 script/plot_sliced_stand.py plate_1.gcode output_directory This is inspection only: no G-code is modified or sent to a printer. """ import math import os import re import sys import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.collections import LineCollection from mpl_toolkits.mplot3d.art3d import Line3DCollection import numpy as np source, output = sys.argv[1:] os.makedirs(output, exist_ok=True) position = {a: 0.0 for a in "XYZE"} relative_e, relative_xyz = False, False feature = "Custom" segments = {"model": [], "support": []} with open(source) as stream: for line in stream: if line.startswith("; FEATURE:"): feature = line.partition(":")[2].strip() code = line.partition(";")[0].strip() if not code: continue command = code.split()[0] values = {a: float(v) for a, v in re.findall(r"([XYZEIJ])(-?(?:\d+(?:\.\d*)?|\.\d+))", code)} if command == "M83": relative_e = True elif command == "M82": relative_e = False elif command == "G91": relative_xyz = True elif command == "G90": relative_xyz = False elif command == "G92": position.update({a: v for a, v in values.items() if a in position}) elif command in ("G0", "G1", "G2", "G3"): old = position.copy() for a in "XYZE": if a in values: relative = relative_e if a == "E" else relative_xyz position[a] = values[a] + (old[a] if relative else 0.0) if position["E"] <= old["E"] or feature in ("Custom", "Flush", "Prime tower", "Skirt"): continue start, end = [old[a] for a in "XYZ"], [position[a] for a in "XYZ"] points = [start, end] if command in ("G2", "G3") and ("I" in values or "J" in values): cx, cy = start[0] + values.get("I", 0), start[1] + values.get("J", 0) radius = math.hypot(start[0]-cx, start[1]-cy) a = math.atan2(start[1]-cy, start[0]-cx) b = math.atan2(end[1]-cy, end[0]-cx) sweep = (b-a) % (2*math.pi) if command == "G3" else -((a-b) % (2*math.pi)) count = max(2, math.ceil(abs(sweep)*radius / 0.3)) points = [[cx+radius*math.cos(a+sweep*t/count), cy+radius*math.sin(a+sweep*t/count), start[2]+(end[2]-start[2])*t/count] for t in range(count+1)] group = "support" if feature.startswith("Support") else "model" segments[group].extend(zip(points, points[1:])) paths = {name: np.asarray(items).reshape(-1, 2, 3) for name, items in segments.items()} all_points = np.concatenate(list(paths.values())).reshape(-1, 3) low, high = all_points.min(axis=0), all_points.max(axis=0) colors = {"model": "#586879", "support": "#de790e"} for name, elevation, azimuth in [("iso", 25, -65), ("front", 0, -90), ("top", 90, -90)]: fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(projection="3d") for group, paths_for_group in paths.items(): ax.add_collection3d(Line3DCollection(paths_for_group, colors=colors[group], linewidths=0.3, alpha=0.85)) ax.set(xlim=(low[0], high[0]), ylim=(low[1], high[1]), zlim=(0, high[2])) ax.set_box_aspect(high-low) ax.view_init(elev=elevation, azim=azimuth) ax.set_title("Sliced stand — gray: model; orange: supports") ax.set_axis_off() fig.savefig(os.path.join(output, name+".png"), dpi=150, bbox_inches="tight") plt.close(fig) fig, axes = plt.subplots(2, 3, figsize=(14, 9)) for ax, z in zip(axes.flat, [0.2, 5.0, 15.0, 24.0, 30.0, 35.0]): for group, items in paths.items(): subset = items[np.abs(items[:, 1, 2]-z) < 0.025] ax.add_collection(LineCollection(subset[:, :, :2], colors=colors[group], linewidths=0.5)) ax.set(xlim=(low[0], high[0]), ylim=(low[1], high[1]), title=f"Z = {z:.1f} mm", aspect="equal") fig.suptitle("Actual sliced layers — gray: model; orange: supports") fig.tight_layout() fig.savefig(os.path.join(output, "layers.png"), dpi=150) print(f"Plotted {sum(len(v) for v in segments.values())} extrusion segments in {output}")