"""Grouper: true sections of the built model (read-only): the meshes cut by planes Y = const (or X = const), drawn as (x, z) segments per object, for checking clearances by eye. Writes JSON for gr_section_plot.py (plain Python + PIL). blender.exe -b Tender_Grouper.blend --python build/gr_section.py -- y=-2.70 y=-2.65 x=0.45 out=""" import bpy, sys, json import numpy as np args = sys.argv[sys.argv.index("--") + 1:] cuts = [(a[0], float(a[2:])) for a in args if a[:2] in ("y=", "x=")] out = [a[4:] for a in args if a.startswith("out=")][0] dg = bpy.context.evaluated_depsgraph_get() res = {} for o in bpy.data.objects: if o.type != 'MESH' or o.name.startswith("UCX_") or o.hide_render: continue ev = o.evaluated_get(dg) me = ev.to_mesh() me.calc_loop_triangles() M_ = np.array(ev.matrix_world) V = np.array([v.co[:] for v in me.vertices] or [[0, 0, 0]]) V = (np.c_[V, np.ones(len(V))] @ M_.T)[:, :3] T = np.array([t.vertices[:] for t in me.loop_triangles] or [[0, 0, 0]]) ev.to_mesh_clear() for ax, val in cuts: ai = 1 if ax == "y" else 0 d = V[:, ai] - val segs = [] dt = d[T] cross = (np.sign(dt).max(1) > 0) & (np.sign(dt).min(1) < 0) for tri in T[cross]: pts = [] for a, b in ((0, 1), (1, 2), (2, 0)): da, db = d[tri[a]], d[tri[b]] if (da > 0) != (db > 0): t = da / (da - db) p = V[tri[a]] + (V[tri[b]] - V[tri[a]]) * t pts.append(p) if len(pts) == 2: if ax == "y": segs.append([round(pts[0][0], 4), round(pts[0][2], 4), round(pts[1][0], 4), round(pts[1][2], 4)]) else: segs.append([round(pts[0][1], 4), round(pts[0][2], 4), round(pts[1][1], 4), round(pts[1][2], 4)]) res.setdefault("%s=%.3f" % (ax, val), {})[o.name] = segs json.dump(res, open(out, "w")) print("SECTION OK", list(res.keys()))