#!/usr/bin/env python3 """Audit several bounded face windows and combine their height range.""" import argparse import gc import importlib.util from pathlib import Path import resource def main(): parser = argparse.ArgumentParser() parser.add_argument("scene") parser.add_argument("--surface", choices=("floor", "ceiling"), required=True) parser.add_argument("--center", nargs=2, type=float, action="append", required=True, help="repeat for each bounded local ring") parser.add_argument("--radius", type=float, default=8.0) parser.add_argument("--lod", type=int, required=True) parser.add_argument("--height-min-m", type=float, required=True) parser.add_argument("--height-max-m", type=float, required=True) parser.add_argument("--preferred-height-m", type=float, help="lock overlapping-face selection near this expected surface height") parser.add_argument("--step", type=float, default=0.125) parser.add_argument("--plateau-min-m", type=float, default=0.5) parser.add_argument("--plateau-epsilon-m", type=float, default=0.0005) parser.add_argument("--step-threshold-m", type=float, default=0.1) parser.add_argument("--normal-jump-max-deg", type=float, default=20.0) parser.add_argument("--amplitude-min-m", type=float, default=3.0) parser.add_argument("--min-radial-lines-per-center", type=int, default=4) parser.add_argument("--min-straight-lines-per-center", type=int, default=2) args = parser.parse_args() audit_path = Path(__file__).with_name("AuditRingTerraceL32.py") spec = importlib.util.spec_from_file_location("ring_audit_l32_set", audit_path) audit = importlib.util.module_from_spec(spec) spec.loader.exec_module(audit) upward = args.surface == "floor" preferred_height = args.preferred_height_m if preferred_height is None: preferred_height = args.height_max_m if upward else args.height_min_m all_ranges = [] overall_max_normal = 0.0 overall_max_plateau = 0.0 overall_max_step = 0.0 overall_min_coverage = 1.0 total_valid_lines = 0 total_lines = 0 failed = False for center in args.center: bounds = (center[0] - args.radius, center[0] + args.radius, center[1] - args.radius, center[1] + args.radius) voxel_um, triangles, selected_tiles = audit.read_scene( args.scene, args.lod, args.height_min_m, args.height_max_m, bounds) max_lod = max((triangle[0] for triangle in triangles), default=0) cell_size = max(2.0, 16.0 * voxel_um / 1_000_000.0 * (1 << max_lod)) buckets = audit.build_buckets(triangles, upward, bounds, cell_size) results = [] for name, points in audit.make_lines(*center, args.radius, args.step): result = audit.audit_profile(points, buckets, upward, args.plateau_min_m, args.plateau_epsilon_m, args.step_threshold_m, cell_size, preferred_height) results.append((name, points, result)) radial = [row for row in results if row[0].startswith("radial-") and row[2]["valid"]] straight = [row for row in results if row[0].startswith("straight-") and row[2]["valid"]] valid = [row for row in results if row[2]["valid"]] local_min = min((row[2]["height_min"] for row in valid), default=float("inf")) local_max = max((row[2]["height_max"] for row in valid), default=float("-inf")) local_amplitude = local_max - local_min local_plateau = max((run[0] for row in valid for run in row[2]["plateaus"]), default=0.0) local_step = max((run[1] for row in valid for run in row[2]["plateaus"]), default=0.0) local_normal = max((row[2]["max_angle"] for row in valid), default=float("inf")) local_coverage = min((row[2]["coverage"] for row in results), default=0.0) local_ok = (len(valid) == len(results) and len(radial) >= args.min_radial_lines_per_center and len(straight) >= args.min_straight_lines_per_center and local_plateau < args.plateau_min_m and local_normal <= args.normal_jump_max_deg) all_ranges.extend((row[2]["height_min"], row[2]["height_max"]) for row in valid) overall_max_normal = max(overall_max_normal, local_normal) overall_max_plateau = max(overall_max_plateau, local_plateau) overall_max_step = max(overall_max_step, local_step) overall_min_coverage = min(overall_min_coverage, local_coverage) total_valid_lines += len(valid) total_lines += len(results) print(f"[RingSetWindow] center_m=({center[0]:.3f},{center[1]:.3f}) " f"radius_m={args.radius:.3f} tiles={selected_tiles} lines={len(valid)}/" f"{len(results)} radial={len(radial)} straight={len(straight)} " f"coverage_min={local_coverage:.3f} height_m={local_min:.6f}.." f"{local_max:.6f} amplitude_m={local_amplitude:.6f} " f"longest_flat_m={local_plateau:.6f} following_step_m={local_step:.6f} " f"max_normal_delta_deg={local_normal:.6f} " f"status={'PASS' if local_ok else 'FAIL'}") for name, _, result in results: print(f"[RingSetProfile] center_m=({center[0]:.3f},{center[1]:.3f}) " f"line={name} coverage={result['coverage']:.3f} " f"range_m={result.get('height_range', 0.0):.6f} " f"flat_step_runs={len(result['plateaus'])} " f"max_normal_delta_deg={result['max_angle']:.6f}") failed |= not local_ok del triangles, buckets, results, radial, straight, valid gc.collect() if all_ranges: sampled_min = min(value[0] for value in all_ranges) sampled_max = max(value[1] for value in all_ranges) amplitude = sampled_max - sampled_min else: sampled_min = sampled_max = amplitude = 0.0 passed = not failed and amplitude >= args.amplitude_min_m print(f"[RingSetAudit] scene={args.scene} surface={args.surface} lod={args.lod} " f"windows={len(args.center)} radius_m={args.radius:.3f} " f"height_window_m={args.height_min_m:.3f}..{args.height_max_m:.3f} " f"sampled_height_m={sampled_min:.6f}..{sampled_max:.6f} " f"amplitude_range_m={amplitude:.6f} lines={total_valid_lines}/{total_lines} " f"coverage_min={overall_min_coverage:.3f} " f"longest_flat_run_m={overall_max_plateau:.6f} " f"max_step_m={overall_max_step:.6f} " f"max_normal_delta_deg={overall_max_normal:.6f} " f"thresholds=(amplitude>={args.amplitude_min_m:.3f}," f"plateau<{args.plateau_min_m:.3f}," f"normal<={args.normal_jump_max_deg:.3f}) " f"status={'PASS' if passed else 'FAIL'}") print(f"[RingSetRSS] peak_rss_kib={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}") raise SystemExit(0 if passed else 1) if __name__ == "__main__": main()