Spaces:
Running
Running
| """ | |
| Quality metrics for folded origami. | |
| Computes bounding box, deployment ratio, fold count, and aggregated | |
| metric dictionaries for the trainer reward functions. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from .paper import Paper | |
| def compute_bounding_box(paper: Paper) -> np.ndarray: | |
| """Axis-aligned bounding-box dimensions (dx, dy, dz). | |
| Returns shape (3,) array. Minimum z-thickness accounts for | |
| material thickness times estimated layer count. | |
| """ | |
| if len(paper.vertices) == 0: | |
| return np.zeros(3) | |
| ptp = np.ptp(paper.vertices, axis=0) | |
| ptp = np.where(np.abs(ptp) < 1e-12, 0.0, ptp) | |
| # Minimum z from material thickness * layers | |
| t = paper.material.thickness_mm / 1000.0 | |
| ptp[2] = max(ptp[2], t * paper.num_layers) | |
| return ptp | |
| def compute_deployment_ratio(paper: Paper) -> float: | |
| """Ratio of folded XY footprint area to original sheet area. | |
| A fully flat unfolded sheet has ratio 1.0; a tightly folded sheet | |
| approaches 0.0. | |
| """ | |
| if paper.original_area <= 0: | |
| return 1.0 | |
| bb = compute_bounding_box(paper) | |
| folded_area = bb[0] * bb[1] | |
| ratio = folded_area / paper.original_area | |
| return float(np.clip(ratio, 0.0, 1.0)) | |
| def compute_fold_count(paper: Paper) -> int: | |
| """Number of mountain (M) and valley (V) edges.""" | |
| return sum(1 for a in paper.assignments if a in ("M", "V")) | |
| def compute_compactness(paper: Paper) -> float: | |
| """1 - deployment_ratio. Higher is more compact.""" | |
| return 1.0 - compute_deployment_ratio(paper) | |
| def compute_volume(paper: Paper) -> float: | |
| """Bounding-box volume in cubic meters.""" | |
| bb = compute_bounding_box(paper) | |
| return float(bb[0] * bb[1] * bb[2]) | |
| def compute_metrics(paper: Paper, original_paper: Paper | None = None) -> dict: | |
| """Compute all quality metrics and return as a dict. | |
| Parameters | |
| ---------- | |
| paper : Paper | |
| The current (folded) paper state. | |
| original_paper : Paper or None | |
| The original (unfolded) paper, used for strain comparison. | |
| If None, strain is computed against the current paper's rest lengths. | |
| Returns | |
| ------- | |
| dict with keys: | |
| bounding_box, deployment_ratio, fold_count, compactness, | |
| volume, max_strain, mean_strain, num_vertices, num_faces, | |
| num_layers. | |
| """ | |
| from .physics import compute_strain # local import to avoid circular | |
| bb = compute_bounding_box(paper) | |
| strain = compute_strain(paper) | |
| return { | |
| "bounding_box": { | |
| "x": float(bb[0]), | |
| "y": float(bb[1]), | |
| "z": float(bb[2]), | |
| }, | |
| "deployment_ratio": compute_deployment_ratio(paper), | |
| "fold_count": compute_fold_count(paper), | |
| "compactness": compute_compactness(paper), | |
| "volume": compute_volume(paper), | |
| "max_strain": float(np.max(strain)) if len(strain) > 0 else 0.0, | |
| "mean_strain": float(np.mean(strain)) if len(strain) > 0 else 0.0, | |
| "num_vertices": len(paper.vertices), | |
| "num_faces": len(paper.faces), | |
| "num_layers": paper.num_layers, | |
| } | |