"""Count connected components in a slab of an OBJ, to tell separated digits from a fused mitt. Slice just past the knuckles: 4 components = separate fingers, 1 = fused paddle. usage: obj_slice_components.py mesh.obj AXIS LO HI (axis x|y|z, bounds in metres) """ import sys from pathlib import Path from collections import defaultdict, deque path, axis, lo, hi = sys.argv[1], sys.argv[2], float(sys.argv[3]), float(sys.argv[4]) ai = {"x": 0, "y": 1, "z": 2}[axis] vs, faces = [], [] for line in Path(path).read_text().splitlines(): if line.startswith("v "): p = line.split() vs.append((float(p[1]), float(p[2]), float(p[3]))) elif line.startswith("f "): faces.append([int(t.split("/")[0]) - 1 for t in line.split()[1:]]) inslab = [i for i, v in enumerate(vs) if lo <= v[ai] <= hi] sel = set(inslab) adj = defaultdict(set) kept = 0 for f in faces: if all(k in sel for k in f): kept += 1 for a in f: for b in f: if a != b: adj[a].add(b) seen = set() comps = [] for v in inslab: if v in seen: continue q = deque([v]); seen.add(v); c = [] while q: u = q.popleft(); c.append(u) for w in adj.get(u, ()): if w not in seen: seen.add(w); q.append(w) comps.append(c) comps.sort(key=len, reverse=True) print(f"{Path(path).name} slab {axis} in [{lo}, {hi}]") print(f" verts in slab {len(inslab)}, faces kept {kept}, components {len(comps)}") for n, c in enumerate(comps[:10]): if len(c) < 4: continue ext = [(min(vs[k][d] for k in c) * 100, max(vs[k][d] for k in c) * 100) for d in range(3)] span = [f"{e[1]-e[0]:.1f}" for e in ext] ctr = [f"{(e[0]+e[1])/2:.1f}" for e in ext] print(f" comp{n}: {len(c):5d} verts span(cm) x{span[0]} y{span[1]} z{span[2]}" f" centre({ctr[0]}, {ctr[1]}, {ctr[2]})") big = [c for c in comps if len(c) >= 20] print(f" components with >=20 verts: {len(big)}")