#!/usr/bin/env python3 """Inspect connected LOD0 surface components around the FloatingIslands mouth.""" import argparse import importlib.util import math from pathlib import Path ROOT = Path(__file__).resolve().parents[4] PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py" SPEC = importlib.util.spec_from_file_location("ring_profile", PROFILE_PATH) PROFILE = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(PROFILE) def main(): parser = argparse.ArgumentParser() parser.add_argument("mesh") parser.add_argument("--center", nargs=2, type=float, default=(5.6, 0.445)) parser.add_argument("--radius", type=float, default=14.0) parser.add_argument("--z-min", type=float, default=-66.0) parser.add_argument("--z-max", type=float, default=-50.0) parser.add_argument("--minimum-triangles", type=int, default=1) args = parser.parse_args() cx, cy = args.center bounds = (cx - args.radius, cx + args.radius, cy - args.radius, cy + args.radius) voxel_um, triangles, tiles = PROFILE.read_scene( args.mesh, 0, args.z_min, args.z_max, bounds) parent = list(range(len(triangles))) sizes = [1] * len(triangles) vertex_owner = {} def find(index): while parent[index] != index: parent[index] = parent[parent[index]] index = parent[index] return index def union(a, b): a, b = find(a), find(b) if a == b: return if sizes[a] < sizes[b]: a, b = b, a parent[b] = a sizes[a] += sizes[b] for index, (_, _, verts) in enumerate(triangles): for point, _ in verts: key = tuple(round(value, 5) for value in point) other = vertex_owner.setdefault(key, index) union(index, other) components = {} for index, (_, _, verts) in enumerate(triangles): root = find(index) item = components.setdefault(root, {"count": 0, "min": [math.inf] * 3, "max": [-math.inf] * 3, "area": 0.0, "sum": [0.0] * 3}) item["count"] += 1 points = [v[0] for v in verts] for point in points: for axis in range(3): item["min"][axis] = min(item["min"][axis], point[axis]) item["max"][axis] = max(item["max"][axis], point[axis]) item["sum"][axis] += point[axis] ab = tuple(points[1][axis] - points[0][axis] for axis in range(3)) ac = tuple(points[2][axis] - points[0][axis] for axis in range(3)) cross = (ab[1] * ac[2] - ab[2] * ac[1], ab[2] * ac[0] - ab[0] * ac[2], ab[0] * ac[1] - ab[1] * ac[0]) item["area"] += 0.5 * math.sqrt(sum(value * value for value in cross)) ordered = sorted(components.values(), key=lambda item: item["count"], reverse=True) print(f"[MouthComponentProbe] mesh={args.mesh} voxel_um={voxel_um} lod=0 " f"tiles={tiles} triangles={len(triangles)} components={len(ordered)} " f"bounds=({bounds[0]:.3f},{bounds[1]:.3f},{bounds[2]:.3f},{bounds[3]:.3f}) " f"z={args.z_min:.3f}..{args.z_max:.3f}") for item in ordered: if item["count"] < args.minimum_triangles: continue centroid = tuple(value / (3 * item["count"]) for value in item["sum"]) print(f"[MouthComponent] triangles={item['count']} area_m2={item['area']:.4f} " f"bounds=({','.join(f'{v:.3f}' for v in item['min'])}).." f"({','.join(f'{v:.3f}' for v in item['max'])}) " f"centroid=({','.join(f'{v:.3f}' for v in centroid)})") if __name__ == "__main__": main()