"""Topology audit of the built meshes (numpy, no Blender): faces by size, n-gons, tiny edges, zero-area faces, open edges, inconsistent winding.""" import numpy as np, json import ae_parts as A, ae_hullmesh as HM from ae_geo import winding_report def audit(mesh): V = np.array(mesh.v); out = {'faces': len(mesh.f)} sizes = {} tiny_e, zero_a = 0, 0 for f in mesh.f: sizes[len(f)] = sizes.get(len(f), 0) + 1 p = V[list(f)] e = np.linalg.norm(np.roll(p, -1, 0) - p, axis=1) tiny_e += int((e < 2e-4).sum()) n = np.zeros(3) for a in range(len(p)): n += np.cross(p[a], p[(a + 1) % len(p)]) zero_a += int(np.linalg.norm(n) / 2 < 1e-8) out.update(by_size=sizes, tiny_edges_under_0_2mm=tiny_e, zero_area=zero_a, **winding_report(mesh)) return out if __name__ == '__main__': o = HM.build() rep = {k: audit(o[k]) for k in ('skin', 'lining', 'frames', 'glass')} for fn in ('nacelle', 'upper_pod', 'side_lamp', 'nose_belt', 'chin', 'roof_block', 'fin', 'fin_logo', 'stern_cap', 'strips'): r = getattr(A, fn)() ms = [x for x in (r if isinstance(r, tuple) else (r,)) if hasattr(x, 'f')] for k, x in enumerate(ms): rep[f'{fn}{k}'] = audit(x) for k, (mm, pv) in A.arm().items(): rep[k] = audit(mm) rep['hatch'] = audit(A.hatch_lid()[0]) rep['cabin'] = audit(A.cabin(o['grid'])) bad = {k: v for k, v in rep.items() if v['tiny_edges_under_0_2mm'] or v['zero_area'] or v['same_direction'] or any(s > 4 for s in v['by_size'])} print(json.dumps(bad, indent=0) if bad else 'clean') print('totals', sum(v['faces'] for v in rep.values()), 'faces;', {s: sum(v['by_size'].get(s, 0) for v in rep.values()) for s in (3, 4)})