Spaces:
Sleeping
Sleeping
File size: 3,105 Bytes
efeed27 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | """
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,
}
|