Spaces:
Running
Running
Commit ·
ca61c8d
1
Parent(s): f8d2bab
feat: update engine modules, remove research 2 docs
Browse files- engine/fold_engine.py +42 -0
- engine/metrics.py +127 -0
- engine/paper.py +38 -1
- engine/physics.py +260 -0
- engine/validation.py +22 -0
- research 2/openenv/2048_example.py +0 -636
- research 2/openenv/2048_pattern.md +0 -74
- research 2/openenv/overview.md +0 -141
- research 2/origami/existing_work.md +0 -51
- research 2/origami/fold_format.md +0 -48
- research 2/origami/fold_types_deep.md +0 -997
- research 2/origami/materials.md +0 -52
- research 2/origami/math_physics_deep.md +0 -1362
- research 2/origami/metrics.md +0 -58
- research 2/origami/origami_simulator_code.md +0 -1030
- research 2/origami/physics.md +0 -37
- research 2/origami/python_tools.md +0 -45
- research 2/origami/rendering_research.md +0 -863
- research 2/origami/simulation_engines.md +0 -65
- research 2/plan/architecture.md +0 -474
- research 2/plan/openenv_arch.md +0 -1523
- research 2/research.md +0 -74
engine/fold_engine.py
CHANGED
|
@@ -151,6 +151,8 @@ def apply_fold(
|
|
| 151 |
elif face_sides[i] == "fixed" and face_sides[j] == "rotated":
|
| 152 |
new_paper.face_orders.append((j, i, 1))
|
| 153 |
|
|
|
|
|
|
|
| 154 |
return new_paper, None
|
| 155 |
|
| 156 |
|
|
@@ -205,3 +207,43 @@ def execute_fold_strategy(
|
|
| 205 |
applied.append(fold)
|
| 206 |
|
| 207 |
return current, applied, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
elif face_sides[i] == "fixed" and face_sides[j] == "rotated":
|
| 152 |
new_paper.face_orders.append((j, i, 1))
|
| 153 |
|
| 154 |
+
new_paper.fold_count += 1
|
| 155 |
+
|
| 156 |
return new_paper, None
|
| 157 |
|
| 158 |
|
|
|
|
| 207 |
applied.append(fold)
|
| 208 |
|
| 209 |
return current, applied, None
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def apply_pleat(
|
| 213 |
+
paper: Paper,
|
| 214 |
+
line1: dict,
|
| 215 |
+
line2: dict,
|
| 216 |
+
angle: float = 180.0,
|
| 217 |
+
) -> tuple[Paper, str | None]:
|
| 218 |
+
"""Pleat fold: valley at line1, mountain at line2 (two parallel folds).
|
| 219 |
+
|
| 220 |
+
Both line dicts have the form: {"start": [x, y], "end": [x, y]}
|
| 221 |
+
Returns (new_paper, error_or_None).
|
| 222 |
+
"""
|
| 223 |
+
paper, err = apply_fold(paper, {"type": "valley", "line": line1, "angle": angle})
|
| 224 |
+
if err:
|
| 225 |
+
return paper, f"Pleat valley fold failed: {err}"
|
| 226 |
+
paper, err = apply_fold(paper, {"type": "mountain", "line": line2, "angle": angle})
|
| 227 |
+
if err:
|
| 228 |
+
return paper, f"Pleat mountain fold failed: {err}"
|
| 229 |
+
return paper, None
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def apply_crimp(
|
| 233 |
+
paper: Paper,
|
| 234 |
+
line1: dict,
|
| 235 |
+
line2: dict,
|
| 236 |
+
angle: float = 180.0,
|
| 237 |
+
) -> tuple[Paper, str | None]:
|
| 238 |
+
"""Crimp fold: mountain at line1, valley at line2 (reverse of pleat).
|
| 239 |
+
|
| 240 |
+
Both line dicts have the form: {"start": [x, y], "end": [x, y]}
|
| 241 |
+
Returns (new_paper, error_or_None).
|
| 242 |
+
"""
|
| 243 |
+
paper, err = apply_fold(paper, {"type": "mountain", "line": line1, "angle": angle})
|
| 244 |
+
if err:
|
| 245 |
+
return paper, f"Crimp mountain fold failed: {err}"
|
| 246 |
+
paper, err = apply_fold(paper, {"type": "valley", "line": line2, "angle": angle})
|
| 247 |
+
if err:
|
| 248 |
+
return paper, f"Crimp valley fold failed: {err}"
|
| 249 |
+
return paper, None
|
engine/metrics.py
CHANGED
|
@@ -102,3 +102,130 @@ def compute_metrics(paper: Paper, original_paper: Paper | None = None) -> dict:
|
|
| 102 |
"num_faces": len(paper.faces),
|
| 103 |
"num_layers": paper.num_layers,
|
| 104 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
"num_faces": len(paper.faces),
|
| 103 |
"num_layers": paper.num_layers,
|
| 104 |
}
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def compute_all_metrics(paper, task: dict, validation: dict) -> dict:
|
| 108 |
+
"""Compute every metric and return a flat dict.
|
| 109 |
+
|
| 110 |
+
Called after physics + validation. Combines validity, compactness,
|
| 111 |
+
structural, efficiency, and deployability metrics.
|
| 112 |
+
|
| 113 |
+
Parameters
|
| 114 |
+
----------
|
| 115 |
+
paper : Paper
|
| 116 |
+
Current paper state (after simulate()).
|
| 117 |
+
task : dict
|
| 118 |
+
Task definition with keys: width, height, target_ratio, target_box, must_deploy.
|
| 119 |
+
validation : dict
|
| 120 |
+
Output of validate_state(paper).
|
| 121 |
+
"""
|
| 122 |
+
import numpy as np
|
| 123 |
+
|
| 124 |
+
bb = paper.bounding_box # (3,) array
|
| 125 |
+
original_area = paper.original_area if paper.original_area > 0 else (paper.material.thickness_mm / 1000.0)
|
| 126 |
+
t = paper.material.thickness_mm / 1000.0
|
| 127 |
+
original_bbox_vol = original_area * t
|
| 128 |
+
folded_bbox_vol = float(bb[0] * bb[1] * bb[2]) if bb[2] > 0 else float(bb[0] * bb[1] * t)
|
| 129 |
+
|
| 130 |
+
# ── Folded area (XY footprint) ────────────────────────────────
|
| 131 |
+
if len(paper.vertices) >= 3:
|
| 132 |
+
try:
|
| 133 |
+
from scipy.spatial import ConvexHull
|
| 134 |
+
hull = ConvexHull(paper.vertices[:, :2])
|
| 135 |
+
folded_area = float(hull.volume)
|
| 136 |
+
except Exception:
|
| 137 |
+
ptp = np.ptp(paper.vertices[:, :2], axis=0)
|
| 138 |
+
folded_area = float(ptp[0] * ptp[1])
|
| 139 |
+
else:
|
| 140 |
+
folded_area = original_area
|
| 141 |
+
|
| 142 |
+
deployment_ratio = folded_area / original_area if original_area > 0 else 1.0
|
| 143 |
+
compactness = 1.0 - deployment_ratio
|
| 144 |
+
volume_compaction = folded_bbox_vol / original_bbox_vol if original_bbox_vol > 0 else 1.0
|
| 145 |
+
material_volume = original_area * t
|
| 146 |
+
packing_efficiency = material_volume / folded_bbox_vol if folded_bbox_vol > 0 else 0.0
|
| 147 |
+
|
| 148 |
+
# ── Target box check ─────────────────────────────────────────
|
| 149 |
+
target_box = task.get("target_box")
|
| 150 |
+
fits_target_box = False
|
| 151 |
+
if target_box and len(target_box) == 3:
|
| 152 |
+
fits_target_box = bool(
|
| 153 |
+
bb[0] <= target_box[0] + 1e-6 and
|
| 154 |
+
bb[1] <= target_box[1] + 1e-6 and
|
| 155 |
+
bb[2] <= target_box[2] + 1e-6
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
# ── Strain ───────────────────────────────────────────────────
|
| 159 |
+
strain = paper.strain_per_vertex
|
| 160 |
+
max_strain = float(np.max(strain)) if len(strain) > 0 else 0.0
|
| 161 |
+
mean_strain = float(np.mean(strain)) if len(strain) > 0 else 0.0
|
| 162 |
+
|
| 163 |
+
# ── Energy ───────────────────────────────────────────────────
|
| 164 |
+
energy = paper.energy
|
| 165 |
+
|
| 166 |
+
# ── Efficiency ───────────────────────────────────────────────
|
| 167 |
+
fold_count = paper.fold_count
|
| 168 |
+
|
| 169 |
+
# Crease complexity: entropy of M/V assignment distribution
|
| 170 |
+
mv_assignments = [a for a in paper.assignments if a in ("M", "V")]
|
| 171 |
+
if mv_assignments:
|
| 172 |
+
total = len(mv_assignments)
|
| 173 |
+
m_count = mv_assignments.count("M")
|
| 174 |
+
v_count = mv_assignments.count("V")
|
| 175 |
+
p_m = m_count / total if total > 0 else 0
|
| 176 |
+
p_v = v_count / total if total > 0 else 0
|
| 177 |
+
crease_complexity = 0.0
|
| 178 |
+
if p_m > 0:
|
| 179 |
+
crease_complexity -= p_m * np.log2(p_m)
|
| 180 |
+
if p_v > 0:
|
| 181 |
+
crease_complexity -= p_v * np.log2(p_v)
|
| 182 |
+
else:
|
| 183 |
+
crease_complexity = 0.0
|
| 184 |
+
|
| 185 |
+
folding_efficiency = compactness / max(fold_count, 1)
|
| 186 |
+
|
| 187 |
+
# ── Deployability ─────────────────────────────────────────────
|
| 188 |
+
must_deploy = task.get("must_deploy", False)
|
| 189 |
+
# Simple deployability heuristic: if valid and compactness > 0, assume deployable
|
| 190 |
+
is_deployable = bool(validation.get("is_valid", False) and compactness > 0.01) if must_deploy else None
|
| 191 |
+
# Deployment force estimate from total energy gradient (rough)
|
| 192 |
+
deployment_force_estimate = float(energy.get("fold", 0.0)) / max(paper.original_area, 1e-6)
|
| 193 |
+
|
| 194 |
+
return {
|
| 195 |
+
# Validity (from validation dict)
|
| 196 |
+
"is_valid": validation.get("is_valid", False),
|
| 197 |
+
"kawasaki_violations": validation.get("kawasaki_violations", 0),
|
| 198 |
+
"kawasaki_total_error": validation.get("kawasaki_total_error", 0.0),
|
| 199 |
+
"maekawa_violations": validation.get("maekawa_violations", 0),
|
| 200 |
+
"self_intersections": validation.get("self_intersections", 0),
|
| 201 |
+
"strain_exceeded": validation.get("strain_exceeded", False),
|
| 202 |
+
|
| 203 |
+
# Compactness
|
| 204 |
+
"deployment_ratio": float(deployment_ratio),
|
| 205 |
+
"compactness": float(compactness),
|
| 206 |
+
"volume_compaction": float(volume_compaction),
|
| 207 |
+
"packing_efficiency": float(packing_efficiency),
|
| 208 |
+
"fits_target_box": fits_target_box,
|
| 209 |
+
"bounding_box": bb.tolist(),
|
| 210 |
+
|
| 211 |
+
# Structural
|
| 212 |
+
"max_strain": max_strain,
|
| 213 |
+
"mean_strain": mean_strain,
|
| 214 |
+
"total_energy": float(energy.get("total", 0.0)),
|
| 215 |
+
"energy_bar": float(energy.get("bar", 0.0)),
|
| 216 |
+
"energy_facet": float(energy.get("facet", 0.0)),
|
| 217 |
+
"energy_fold": float(energy.get("fold", 0.0)),
|
| 218 |
+
|
| 219 |
+
# Efficiency
|
| 220 |
+
"fold_count": fold_count,
|
| 221 |
+
"folding_efficiency": float(folding_efficiency),
|
| 222 |
+
"crease_complexity": float(crease_complexity),
|
| 223 |
+
|
| 224 |
+
# Deployability
|
| 225 |
+
"is_deployable": is_deployable,
|
| 226 |
+
"deployment_force_estimate": float(deployment_force_estimate),
|
| 227 |
+
|
| 228 |
+
# Shape similarity placeholders
|
| 229 |
+
"chamfer_distance": None,
|
| 230 |
+
"hausdorff_distance": None,
|
| 231 |
+
}
|
engine/paper.py
CHANGED
|
@@ -89,6 +89,10 @@ class Paper:
|
|
| 89 |
material: Material = field(default_factory=lambda: get_material("paper"))
|
| 90 |
rest_lengths: np.ndarray = field(default_factory=lambda: np.empty(0))
|
| 91 |
original_area: float = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
# ── constructors ────────────────────────────────────────────────
|
| 94 |
|
|
@@ -125,7 +129,7 @@ class Paper:
|
|
| 125 |
dtype=np.float64,
|
| 126 |
)
|
| 127 |
|
| 128 |
-
|
| 129 |
vertices=verts,
|
| 130 |
edges=edges,
|
| 131 |
faces=faces,
|
|
@@ -135,6 +139,8 @@ class Paper:
|
|
| 135 |
rest_lengths=rest_lengths,
|
| 136 |
original_area=width * height,
|
| 137 |
)
|
|
|
|
|
|
|
| 138 |
|
| 139 |
# ── dict / prompt serialization (matches mock_env.PaperState.to_dict) ──
|
| 140 |
|
|
@@ -165,6 +171,33 @@ class Paper:
|
|
| 165 |
},
|
| 166 |
}
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
# ── FOLD format serialization ───────────────────────────────────
|
| 169 |
|
| 170 |
def to_fold_json(self) -> str:
|
|
@@ -485,4 +518,8 @@ class Paper:
|
|
| 485 |
),
|
| 486 |
rest_lengths=self.rest_lengths.copy(),
|
| 487 |
original_area=self.original_area,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
)
|
|
|
|
| 89 |
material: Material = field(default_factory=lambda: get_material("paper"))
|
| 90 |
rest_lengths: np.ndarray = field(default_factory=lambda: np.empty(0))
|
| 91 |
original_area: float = 0.0
|
| 92 |
+
rest_positions: np.ndarray = field(default_factory=lambda: np.empty((0, 3)))
|
| 93 |
+
strain_per_vertex: np.ndarray = field(default_factory=lambda: np.empty(0))
|
| 94 |
+
energy: dict = field(default_factory=lambda: {"total": 0.0, "bar": 0.0, "facet": 0.0, "fold": 0.0})
|
| 95 |
+
fold_count: int = 0
|
| 96 |
|
| 97 |
# ── constructors ────────────────────────────────────────────────
|
| 98 |
|
|
|
|
| 129 |
dtype=np.float64,
|
| 130 |
)
|
| 131 |
|
| 132 |
+
paper = Paper(
|
| 133 |
vertices=verts,
|
| 134 |
edges=edges,
|
| 135 |
faces=faces,
|
|
|
|
| 139 |
rest_lengths=rest_lengths,
|
| 140 |
original_area=width * height,
|
| 141 |
)
|
| 142 |
+
paper.rest_positions = verts.copy()
|
| 143 |
+
return paper
|
| 144 |
|
| 145 |
# ── dict / prompt serialization (matches mock_env.PaperState.to_dict) ──
|
| 146 |
|
|
|
|
| 171 |
},
|
| 172 |
}
|
| 173 |
|
| 174 |
+
def to_observation_dict(self) -> dict:
|
| 175 |
+
bb = self.bounding_box
|
| 176 |
+
return {
|
| 177 |
+
"vertices_coords": self.vertices.tolist(),
|
| 178 |
+
"edges_vertices": self.edges.tolist(),
|
| 179 |
+
"faces_vertices": self.faces,
|
| 180 |
+
"edges_assignment": list(self.assignments),
|
| 181 |
+
"edges_foldAngle": self.fold_angles.tolist(),
|
| 182 |
+
"num_vertices": len(self.vertices),
|
| 183 |
+
"num_edges": len(self.edges),
|
| 184 |
+
"num_faces": len(self.faces),
|
| 185 |
+
"bounding_box": bb.tolist(),
|
| 186 |
+
"num_layers": self.num_layers,
|
| 187 |
+
"material": {
|
| 188 |
+
"name": self.material.name,
|
| 189 |
+
"thickness_mm": self.material.thickness_mm,
|
| 190 |
+
"youngs_modulus_gpa": self.material.youngs_modulus_gpa,
|
| 191 |
+
"max_strain": self.material.max_strain,
|
| 192 |
+
"poisson_ratio": self.material.poissons_ratio,
|
| 193 |
+
},
|
| 194 |
+
"strain_per_vertex": self.strain_per_vertex.tolist(),
|
| 195 |
+
"energy": dict(self.energy),
|
| 196 |
+
"fold_count": self.fold_count,
|
| 197 |
+
"width": float(self.original_area ** 0.5) if self.original_area > 0 else 1.0,
|
| 198 |
+
"height": float(self.original_area ** 0.5) if self.original_area > 0 else 1.0,
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
# ── FOLD format serialization ───────────────────────────────────
|
| 202 |
|
| 203 |
def to_fold_json(self) -> str:
|
|
|
|
| 518 |
),
|
| 519 |
rest_lengths=self.rest_lengths.copy(),
|
| 520 |
original_area=self.original_area,
|
| 521 |
+
rest_positions=self.rest_positions.copy(),
|
| 522 |
+
strain_per_vertex=self.strain_per_vertex.copy(),
|
| 523 |
+
energy=dict(self.energy),
|
| 524 |
+
fold_count=self.fold_count,
|
| 525 |
)
|
engine/physics.py
CHANGED
|
@@ -255,3 +255,263 @@ def _face_normal(verts: np.ndarray, face: list[int]) -> np.ndarray | None:
|
|
| 255 |
if norm < 1e-15:
|
| 256 |
return None
|
| 257 |
return normal / norm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
if norm < 1e-15:
|
| 256 |
return None
|
| 257 |
return normal / norm
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
# ────────────────────────────────────────────────────────────────────
|
| 261 |
+
# Topology precomputation
|
| 262 |
+
# ────────────────────────────────────────────────────────────────────
|
| 263 |
+
|
| 264 |
+
def build_beam_list(paper: Paper) -> list[tuple[int, int, float, float]]:
|
| 265 |
+
"""Build list of (node_a, node_b, rest_len, k_axial) for every edge.
|
| 266 |
+
|
| 267 |
+
Uses normalized stiffness values (arch doc constants) scaled by material
|
| 268 |
+
Young's modulus ratio — keeps the Verlet integrator stable at unit scale.
|
| 269 |
+
"""
|
| 270 |
+
# Normalized stiffness constants (arch doc values)
|
| 271 |
+
K_AXIAL_BASE = 70.0
|
| 272 |
+
# Scale by material: paper (3 GPa) = 1.0 baseline
|
| 273 |
+
mat = paper.material
|
| 274 |
+
E_ratio = mat.youngs_modulus_gpa / 3.0
|
| 275 |
+
k_axial = K_AXIAL_BASE * E_ratio
|
| 276 |
+
|
| 277 |
+
beams = []
|
| 278 |
+
for ei, (v1, v2) in enumerate(paper.edges):
|
| 279 |
+
L0 = paper.rest_lengths[ei]
|
| 280 |
+
beams.append((int(v1), int(v2), float(L0), float(k_axial)))
|
| 281 |
+
return beams
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def build_crease_list(paper: Paper) -> list[tuple[int, int, int, int, float, float, str]]:
|
| 285 |
+
"""Build list of (n1, n2, n3, n4, target_angle_rad, k, type) for each crease hinge.
|
| 286 |
+
|
| 287 |
+
Each hinge is defined by 4 nodes: n1-n2 is the hinge edge, n3 and n4 are
|
| 288 |
+
the wing-tip nodes of the two adjacent faces.
|
| 289 |
+
type is 'fold' (M/V crease) or 'facet' (interior flat edge).
|
| 290 |
+
"""
|
| 291 |
+
verts = paper.vertices
|
| 292 |
+
|
| 293 |
+
# Build edge → face adjacency
|
| 294 |
+
edge_faces: dict[int, list[int]] = {}
|
| 295 |
+
for fi, face in enumerate(paper.faces):
|
| 296 |
+
n = len(face)
|
| 297 |
+
for k in range(n):
|
| 298 |
+
va, vb = face[k], face[(k + 1) % n]
|
| 299 |
+
for ei, e in enumerate(paper.edges):
|
| 300 |
+
if (e[0] == va and e[1] == vb) or (e[0] == vb and e[1] == va):
|
| 301 |
+
edge_faces.setdefault(ei, []).append(fi)
|
| 302 |
+
break
|
| 303 |
+
|
| 304 |
+
creases = []
|
| 305 |
+
for ei, adj in edge_faces.items():
|
| 306 |
+
if len(adj) < 2:
|
| 307 |
+
continue
|
| 308 |
+
f1, f2 = adj[0], adj[1]
|
| 309 |
+
face1, face2 = paper.faces[f1], paper.faces[f2]
|
| 310 |
+
n1, n2 = int(paper.edges[ei][0]), int(paper.edges[ei][1])
|
| 311 |
+
|
| 312 |
+
# Find wing-tip nodes (in each face, the vertex NOT on the shared edge)
|
| 313 |
+
wing1 = [v for v in face1 if v != n1 and v != n2]
|
| 314 |
+
wing2 = [v for v in face2 if v != n1 and v != n2]
|
| 315 |
+
if not wing1 or not wing2:
|
| 316 |
+
continue
|
| 317 |
+
n3, n4 = int(wing1[0]), int(wing2[0])
|
| 318 |
+
|
| 319 |
+
# Normalized stiffness constants (arch doc values), scaled by material
|
| 320 |
+
E_ratio = paper.material.youngs_modulus_gpa / 3.0
|
| 321 |
+
K_FACET = 0.2 * E_ratio
|
| 322 |
+
K_FOLD = 0.7 * E_ratio
|
| 323 |
+
|
| 324 |
+
asgn = paper.assignments[ei]
|
| 325 |
+
if asgn in ("M", "V"):
|
| 326 |
+
target = float(np.radians(paper.fold_angles[ei]))
|
| 327 |
+
k = K_FOLD
|
| 328 |
+
ctype = "fold"
|
| 329 |
+
else:
|
| 330 |
+
target = float(np.pi)
|
| 331 |
+
k = K_FACET
|
| 332 |
+
ctype = "facet"
|
| 333 |
+
|
| 334 |
+
creases.append((n1, n2, n3, n4, target, k, ctype))
|
| 335 |
+
return creases
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def _torque_to_forces(
|
| 339 |
+
p1: np.ndarray, p2: np.ndarray,
|
| 340 |
+
p3: np.ndarray, p4: np.ndarray,
|
| 341 |
+
torque: float,
|
| 342 |
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
| 343 |
+
"""Convert a dihedral torque into forces on the 4 hinge nodes.
|
| 344 |
+
|
| 345 |
+
p1-p2 is the hinge edge. p3 and p4 are wing tips.
|
| 346 |
+
Returns (f1, f2, f3, f4) as (3,) arrays.
|
| 347 |
+
"""
|
| 348 |
+
e = p2 - p1
|
| 349 |
+
e_len = np.linalg.norm(e)
|
| 350 |
+
if e_len < 1e-12:
|
| 351 |
+
zero = np.zeros(3)
|
| 352 |
+
return zero, zero, zero, zero
|
| 353 |
+
|
| 354 |
+
e_hat = e / e_len
|
| 355 |
+
|
| 356 |
+
# Perpendicular components of wing vectors relative to hinge
|
| 357 |
+
d3 = p3 - p1
|
| 358 |
+
d4 = p4 - p1
|
| 359 |
+
d3_perp = d3 - np.dot(d3, e_hat) * e_hat
|
| 360 |
+
d4_perp = d4 - np.dot(d4, e_hat) * e_hat
|
| 361 |
+
|
| 362 |
+
len3 = np.linalg.norm(d3_perp)
|
| 363 |
+
len4 = np.linalg.norm(d4_perp)
|
| 364 |
+
|
| 365 |
+
if len3 < 1e-12 or len4 < 1e-12:
|
| 366 |
+
zero = np.zeros(3)
|
| 367 |
+
return zero, zero, zero, zero
|
| 368 |
+
|
| 369 |
+
# Force on wing tips proportional to torque / lever arm
|
| 370 |
+
f3 = torque / (len3 * e_len) * np.cross(e_hat, d3_perp / len3)
|
| 371 |
+
f4 = -torque / (len4 * e_len) * np.cross(e_hat, d4_perp / len4)
|
| 372 |
+
|
| 373 |
+
# Reaction forces distributed to hinge nodes
|
| 374 |
+
f1 = -(f3 + f4) * 0.5
|
| 375 |
+
f2 = -(f3 + f4) * 0.5
|
| 376 |
+
|
| 377 |
+
return f1, f2, f3, f4
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
# ────────────────────────────────────────────────────────────────────
|
| 381 |
+
# Verlet solver
|
| 382 |
+
# ────────────────────────────────────────────────────────────────────
|
| 383 |
+
|
| 384 |
+
def simulate(
|
| 385 |
+
paper: Paper,
|
| 386 |
+
fold_percent: float = 1.0,
|
| 387 |
+
n_steps: int = 500,
|
| 388 |
+
dt: float = 0.005,
|
| 389 |
+
damping: float = 0.15,
|
| 390 |
+
) -> Paper:
|
| 391 |
+
"""Run bar-and-hinge Verlet integration to relax the mesh.
|
| 392 |
+
|
| 393 |
+
Updates paper.vertices, paper.strain_per_vertex, and paper.energy in-place.
|
| 394 |
+
Returns the mutated paper for chaining.
|
| 395 |
+
|
| 396 |
+
Parameters
|
| 397 |
+
----------
|
| 398 |
+
paper : Paper
|
| 399 |
+
Paper state after a fold has been applied (vertices already rotated).
|
| 400 |
+
fold_percent : float
|
| 401 |
+
How far along the fold to drive (0=flat, 1=full target angle).
|
| 402 |
+
n_steps : int
|
| 403 |
+
Maximum integration steps.
|
| 404 |
+
dt : float
|
| 405 |
+
Time step. Keep small (0.005) for stability with stiff materials.
|
| 406 |
+
damping : float
|
| 407 |
+
Velocity damping coefficient (0=undamped, 1=fully damped).
|
| 408 |
+
"""
|
| 409 |
+
if len(paper.vertices) == 0:
|
| 410 |
+
return paper
|
| 411 |
+
|
| 412 |
+
beams = build_beam_list(paper)
|
| 413 |
+
creases = build_crease_list(paper)
|
| 414 |
+
|
| 415 |
+
pos = paper.vertices.copy() # (N, 3) current positions
|
| 416 |
+
last_pos = pos.copy() # (N, 3) previous positions (Verlet)
|
| 417 |
+
|
| 418 |
+
max_force_cap = 1e6 # prevent runaway forces
|
| 419 |
+
|
| 420 |
+
for _ in range(n_steps):
|
| 421 |
+
forces = np.zeros_like(pos)
|
| 422 |
+
|
| 423 |
+
# ── Beam (axial spring) forces ───────────────────────────────
|
| 424 |
+
for (a, b, L0, k) in beams:
|
| 425 |
+
delta = pos[b] - pos[a]
|
| 426 |
+
L = np.linalg.norm(delta)
|
| 427 |
+
if L < 1e-12:
|
| 428 |
+
continue
|
| 429 |
+
strain = (L - L0) / L0
|
| 430 |
+
F_mag = k * strain
|
| 431 |
+
F_vec = F_mag * (delta / L)
|
| 432 |
+
# Clamp to prevent instability
|
| 433 |
+
F_vec = np.clip(F_vec, -max_force_cap, max_force_cap)
|
| 434 |
+
forces[a] += F_vec
|
| 435 |
+
forces[b] -= F_vec
|
| 436 |
+
|
| 437 |
+
# ── Crease (dihedral spring) forces ─────────────────────────
|
| 438 |
+
for (n1, n2, n3, n4, target, k, ctype) in creases:
|
| 439 |
+
actual_target = target * fold_percent if ctype == "fold" else target
|
| 440 |
+
try:
|
| 441 |
+
theta = _compute_dihedral_rad(pos[n1], pos[n2], pos[n3], pos[n4])
|
| 442 |
+
except Exception:
|
| 443 |
+
continue
|
| 444 |
+
delta_theta = theta - actual_target
|
| 445 |
+
edge_len = np.linalg.norm(pos[n2] - pos[n1])
|
| 446 |
+
torque = k * edge_len * delta_theta
|
| 447 |
+
torque = float(np.clip(torque, -max_force_cap, max_force_cap))
|
| 448 |
+
|
| 449 |
+
f1, f2, f3, f4 = _torque_to_forces(
|
| 450 |
+
pos[n1], pos[n2], pos[n3], pos[n4], torque
|
| 451 |
+
)
|
| 452 |
+
forces[n1] += np.clip(f1, -max_force_cap, max_force_cap)
|
| 453 |
+
forces[n2] += np.clip(f2, -max_force_cap, max_force_cap)
|
| 454 |
+
forces[n3] += np.clip(f3, -max_force_cap, max_force_cap)
|
| 455 |
+
forces[n4] += np.clip(f4, -max_force_cap, max_force_cap)
|
| 456 |
+
|
| 457 |
+
# ── Verlet integration ───────────────────────────────────────
|
| 458 |
+
new_pos = pos + (1.0 - damping) * (pos - last_pos) + forces * (dt * dt)
|
| 459 |
+
|
| 460 |
+
# NaN guard
|
| 461 |
+
if np.any(np.isnan(new_pos)):
|
| 462 |
+
break
|
| 463 |
+
|
| 464 |
+
last_pos = pos
|
| 465 |
+
pos = new_pos
|
| 466 |
+
|
| 467 |
+
# ── Convergence check ────────────────────────────────────────
|
| 468 |
+
kinetic = np.sum((pos - last_pos) ** 2)
|
| 469 |
+
if kinetic < 1e-12:
|
| 470 |
+
break
|
| 471 |
+
|
| 472 |
+
# ── Write results back to paper ──────────────────────────────────
|
| 473 |
+
paper.vertices = pos
|
| 474 |
+
paper.strain_per_vertex = compute_strain(paper)
|
| 475 |
+
paper.energy = {
|
| 476 |
+
"total": compute_total_energy(paper),
|
| 477 |
+
"bar": compute_bar_energy(paper),
|
| 478 |
+
"facet": compute_facet_energy(paper),
|
| 479 |
+
"fold": compute_fold_energy(paper),
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
return paper
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
def _compute_dihedral_rad(
|
| 486 |
+
p1: np.ndarray, p2: np.ndarray,
|
| 487 |
+
p3: np.ndarray, p4: np.ndarray,
|
| 488 |
+
) -> float:
|
| 489 |
+
"""Dihedral angle in radians between planes (p1,p2,p3) and (p1,p2,p4).
|
| 490 |
+
|
| 491 |
+
p1-p2 is the hinge edge. p3 and p4 are the wing tips.
|
| 492 |
+
Returns angle in [0, 2*pi).
|
| 493 |
+
"""
|
| 494 |
+
e = p2 - p1
|
| 495 |
+
e_norm = np.linalg.norm(e)
|
| 496 |
+
if e_norm < 1e-12:
|
| 497 |
+
return float(np.pi)
|
| 498 |
+
e_hat = e / e_norm
|
| 499 |
+
|
| 500 |
+
n1 = np.cross(p3 - p1, e)
|
| 501 |
+
n2 = np.cross(e, p4 - p1)
|
| 502 |
+
len1 = np.linalg.norm(n1)
|
| 503 |
+
len2 = np.linalg.norm(n2)
|
| 504 |
+
if len1 < 1e-12 or len2 < 1e-12:
|
| 505 |
+
return float(np.pi)
|
| 506 |
+
|
| 507 |
+
n1 = n1 / len1
|
| 508 |
+
n2 = n2 / len2
|
| 509 |
+
|
| 510 |
+
cos_a = float(np.clip(np.dot(n1, n2), -1.0, 1.0))
|
| 511 |
+
angle = np.arccos(cos_a)
|
| 512 |
+
|
| 513 |
+
cross = np.cross(n1, n2)
|
| 514 |
+
if np.dot(cross, e_hat) < 0:
|
| 515 |
+
angle = 2.0 * np.pi - angle
|
| 516 |
+
|
| 517 |
+
return float(angle)
|
engine/validation.py
CHANGED
|
@@ -254,3 +254,25 @@ def validate_paper(paper: Paper) -> ValidationResult:
|
|
| 254 |
self_intersection_count=si_count,
|
| 255 |
is_valid=k_valid and m_valid and si_valid,
|
| 256 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
self_intersection_count=si_count,
|
| 255 |
is_valid=k_valid and m_valid and si_valid,
|
| 256 |
)
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def validate_state(paper: Paper) -> dict:
|
| 260 |
+
"""Run all validation checks and return a flat dict.
|
| 261 |
+
|
| 262 |
+
This is the interface used by OrigamiEnvironment. It calls the
|
| 263 |
+
existing validation functions and returns a dict with all fields
|
| 264 |
+
the environment and metrics system need.
|
| 265 |
+
"""
|
| 266 |
+
result = validate_paper(paper)
|
| 267 |
+
strain_exceeded = bool(
|
| 268 |
+
len(paper.strain_per_vertex) > 0
|
| 269 |
+
and float(paper.strain_per_vertex.max()) > paper.material.max_strain
|
| 270 |
+
)
|
| 271 |
+
return {
|
| 272 |
+
"is_valid": result.is_valid and not strain_exceeded,
|
| 273 |
+
"kawasaki_violations": int(not result.kawasaki_valid),
|
| 274 |
+
"kawasaki_total_error": float(result.kawasaki_violation),
|
| 275 |
+
"maekawa_violations": int(not result.maekawa_valid),
|
| 276 |
+
"self_intersections": result.self_intersection_count,
|
| 277 |
+
"strain_exceeded": strain_exceeded,
|
| 278 |
+
}
|
research 2/openenv/2048_example.py
DELETED
|
@@ -1,636 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Reference Implementation: OpenEnv + GRPO Reinforcement Learning for 2048 Game
|
| 3 |
-
==============================================================================
|
| 4 |
-
|
| 5 |
-
Extracted from the Unsloth / OpenEnv notebook:
|
| 6 |
-
https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/
|
| 7 |
-
OpenEnv_gpt_oss_(20B)_Reinforcement_Learning_2048_Game.ipynb
|
| 8 |
-
|
| 9 |
-
This file contains ALL code cells from the notebook, organized into sections.
|
| 10 |
-
It serves as a reference for how to build an OpenEnv-based RL environment
|
| 11 |
-
and connect it to GRPO training via TRL.
|
| 12 |
-
|
| 13 |
-
KEY ARCHITECTURE:
|
| 14 |
-
1. OpenEnv provides a server-based game environment (2048 via OpenSpiel)
|
| 15 |
-
2. The LLM generates a Python *strategy function* (code-as-action)
|
| 16 |
-
3. The strategy function is executed against the environment
|
| 17 |
-
4. Three reward functions score the output:
|
| 18 |
-
- function_works: Does the generated code parse and compile?
|
| 19 |
-
- no_cheating: Does it only use stdlib imports?
|
| 20 |
-
- strategy_succeeds: Does the strategy actually play the game well?
|
| 21 |
-
5. GRPO (from TRL) uses these rewards to train the model
|
| 22 |
-
|
| 23 |
-
PROMPT/RESPONSE FORMAT:
|
| 24 |
-
- Prompt asks the LLM to write a Python function `strategy(board)` that
|
| 25 |
-
takes a list-of-lists board state and returns "0","1","2","3" (up/right/down/left)
|
| 26 |
-
- Response is wrapped in ```python ... ``` backticks
|
| 27 |
-
- The function is extracted, sandboxed, and executed against the live game
|
| 28 |
-
|
| 29 |
-
REWARD STRUCTURE:
|
| 30 |
-
- function_works: +1.0 if valid Python, -2.0 if no function / syntax error, -0.5 if exec fails
|
| 31 |
-
- no_cheating: +1.0 if only stdlib imports, -20.0 if non-stdlib imports, -1.0 if no function
|
| 32 |
-
- strategy_succeeds: +20.0 if reaches 2048, +2.0 if function runs but doesn't win,
|
| 33 |
-
-1.0 on timeout, -3.0 on exception, 0 if function broken
|
| 34 |
-
"""
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
# =============================================================================
|
| 38 |
-
# CELL 1: Installation (pip installs - shown for reference, not executable here)
|
| 39 |
-
# =============================================================================
|
| 40 |
-
"""
|
| 41 |
-
%%capture
|
| 42 |
-
import os, importlib.util
|
| 43 |
-
!pip install --upgrade -qqq uv
|
| 44 |
-
if importlib.util.find_spec("torch") is None or "COLAB_" in "".join(os.environ.keys()):
|
| 45 |
-
try: import numpy; get_numpy = f"numpy=={numpy.__version__}"
|
| 46 |
-
except: get_numpy = "numpy"
|
| 47 |
-
!uv pip install -qqq \\
|
| 48 |
-
"torch>=2.8.0" "triton>=3.4.0" {get_numpy} torchvision bitsandbytes "transformers==4.56.2" trackio \\
|
| 49 |
-
"unsloth_zoo[base] @ git+https://github.com/unslothai/unsloth-zoo" \\
|
| 50 |
-
"unsloth[base] @ git+https://github.com/unslothai/unsloth" \\
|
| 51 |
-
git+https://github.com/triton-lang/triton.git@0add68262ab0a2e33b84524346cb27cbb2787356#subdirectory=python/triton_kernels
|
| 52 |
-
elif importlib.util.find_spec("unsloth") is None:
|
| 53 |
-
!uv pip install -qqq unsloth trackio
|
| 54 |
-
!uv pip install --upgrade --no-deps transformers==4.56.2 tokenizers trl==0.22.2 unsloth unsloth_zoo
|
| 55 |
-
"""
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
# =============================================================================
|
| 59 |
-
# CELL 2: Install OpenEnv from source
|
| 60 |
-
# =============================================================================
|
| 61 |
-
"""
|
| 62 |
-
%%capture
|
| 63 |
-
!pip install -qqq fastapi uvicorn requests open_spiel
|
| 64 |
-
!pip install fastapi uvicorn requests
|
| 65 |
-
!pip install open_spiel --prefer-binary
|
| 66 |
-
!git clone https://github.com/meta-pytorch/OpenEnv.git > /dev/null 2>&1
|
| 67 |
-
%cd OpenEnv
|
| 68 |
-
!git checkout 83dda10
|
| 69 |
-
"""
|
| 70 |
-
|
| 71 |
-
import subprocess, sys, os
|
| 72 |
-
from pathlib import Path
|
| 73 |
-
# sys.path.insert(0, '.') # Add OpenEnv root for envs module
|
| 74 |
-
# sys.path.insert(0, './src')
|
| 75 |
-
# working_directory = str(Path.cwd().parent.absolute() / "OpenEnv")
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
# =============================================================================
|
| 79 |
-
# CELL 3: Load the model with Unsloth
|
| 80 |
-
# =============================================================================
|
| 81 |
-
import os
|
| 82 |
-
from unsloth import FastLanguageModel
|
| 83 |
-
import torch
|
| 84 |
-
|
| 85 |
-
max_seq_length = 768 # Can increase for longer RL output
|
| 86 |
-
lora_rank = 4 # Larger rank = smarter, but slower
|
| 87 |
-
|
| 88 |
-
model, tokenizer = FastLanguageModel.from_pretrained(
|
| 89 |
-
model_name = "unsloth/gpt-oss-20b",
|
| 90 |
-
load_in_4bit = True,
|
| 91 |
-
max_seq_length = max_seq_length,
|
| 92 |
-
offload_embedding = True, # Offload embeddings to save more VRAM
|
| 93 |
-
)
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
# =============================================================================
|
| 97 |
-
# CELL 4: Apply LoRA adapters
|
| 98 |
-
# =============================================================================
|
| 99 |
-
model = FastLanguageModel.get_peft_model(
|
| 100 |
-
model,
|
| 101 |
-
r = lora_rank, # Choose any number > 0 ! Suggested 8, 16, 32, 64, 128
|
| 102 |
-
target_modules = [
|
| 103 |
-
"q_proj", "k_proj", "v_proj", "o_proj",
|
| 104 |
-
"gate_proj", "up_proj", "down_proj",
|
| 105 |
-
],
|
| 106 |
-
lora_alpha = lora_rank * 2, # *2 speeds up training
|
| 107 |
-
use_gradient_checkpointing = "unsloth", # Reduces memory usage
|
| 108 |
-
random_state = 3407,
|
| 109 |
-
)
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
# =============================================================================
|
| 113 |
-
# CELL 5: OpenEnv imports (environment-specific)
|
| 114 |
-
# =============================================================================
|
| 115 |
-
from envs.openspiel_env import OpenSpielEnv
|
| 116 |
-
from envs.openspiel_env.models import OpenSpielAction, OpenSpielObservation
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
# =============================================================================
|
| 120 |
-
# CELL 6: OpenEnv process launch configuration
|
| 121 |
-
# =============================================================================
|
| 122 |
-
global port
|
| 123 |
-
global openenv_process
|
| 124 |
-
port = 9000
|
| 125 |
-
openenv_process = None
|
| 126 |
-
server = "envs.openspiel_env.server.app:app"
|
| 127 |
-
environment = {
|
| 128 |
-
**os.environ,
|
| 129 |
-
"PYTHONPATH": f"{working_directory}/src",
|
| 130 |
-
"OPENSPIEL_GAME": "2048",
|
| 131 |
-
"OPENSPIEL_AGENT_PLAYER": "0",
|
| 132 |
-
"OPENSPIEL_OPPONENT_POLICY": "random",
|
| 133 |
-
}
|
| 134 |
-
|
| 135 |
-
# Augment Unsloth's OpenEnv creation function
|
| 136 |
-
import functools
|
| 137 |
-
from unsloth import is_port_open, launch_openenv
|
| 138 |
-
launch_openenv = functools.partial(
|
| 139 |
-
launch_openenv,
|
| 140 |
-
working_directory = working_directory,
|
| 141 |
-
server = server,
|
| 142 |
-
environment = environment,
|
| 143 |
-
openenv_class = OpenSpielEnv,
|
| 144 |
-
)
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
# =============================================================================
|
| 148 |
-
# CELL 7: Reset the environment and observe initial state
|
| 149 |
-
# =============================================================================
|
| 150 |
-
port, openenv_process = launch_openenv(port, openenv_process)
|
| 151 |
-
result = openenv_process.reset()
|
| 152 |
-
current_state = result.observation
|
| 153 |
-
# current_state is an OpenSpielObservation with:
|
| 154 |
-
# .done -> bool
|
| 155 |
-
# .reward -> float or None
|
| 156 |
-
# .info_state -> list of floats (flat board)
|
| 157 |
-
# .legal_actions -> list of ints (e.g. [0,1,2,3])
|
| 158 |
-
# .game_phase -> str
|
| 159 |
-
# .current_player_id -> int
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
# =============================================================================
|
| 163 |
-
# CELL 8: Convert flat info_state to 2D board
|
| 164 |
-
# =============================================================================
|
| 165 |
-
import numpy as np
|
| 166 |
-
|
| 167 |
-
def convert_to_board(current_state):
|
| 168 |
-
n = len(current_state.info_state)
|
| 169 |
-
size = int(np.sqrt(n))
|
| 170 |
-
board = np.array_split(np.array(current_state.info_state, dtype=int), size)
|
| 171 |
-
board = [x.tolist() for x in board]
|
| 172 |
-
return board, size
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
# =============================================================================
|
| 176 |
-
# CELL 9: Pretty-print the 2048 board (collapsible in notebook)
|
| 177 |
-
# =============================================================================
|
| 178 |
-
def render_board(obs, colors: bool = True, border: bool = True, dot_for_zero: bool = True) -> str:
|
| 179 |
-
"""
|
| 180 |
-
Pretty-print the board with colors that scale from 0 up to self.target.
|
| 181 |
-
Uses ANSI 256-color codes (works in most terminals). Set colors=False to disable.
|
| 182 |
-
"""
|
| 183 |
-
import math
|
| 184 |
-
b, size = convert_to_board(obs)
|
| 185 |
-
mx = max((max(row) for row in b), default=0)
|
| 186 |
-
cell_w = max(3, len(str(mx)))
|
| 187 |
-
|
| 188 |
-
RESET = "\x1b[0m"
|
| 189 |
-
|
| 190 |
-
# A smooth-ish gradient from cool -> warm
|
| 191 |
-
GRAD = [33, 39, 45, 51, 50, 49, 48, 47, 46, 82, 118, 154, 190, 226, 220, 214, 208, 202, 196]
|
| 192 |
-
ZERO_FG = 239 # dim gray
|
| 193 |
-
|
| 194 |
-
def color_code(v: int) -> str:
|
| 195 |
-
if not colors:
|
| 196 |
-
return ""
|
| 197 |
-
if v == 0:
|
| 198 |
-
return f"\x1b[38;5;{ZERO_FG}m"
|
| 199 |
-
t = max(2, 2048)
|
| 200 |
-
try:
|
| 201 |
-
r = max(0.0, min(1.0, math.log2(v) / math.log2(t)))
|
| 202 |
-
except ValueError:
|
| 203 |
-
r = 0.0
|
| 204 |
-
idx = int(round(r * (len(GRAD) - 1)))
|
| 205 |
-
return f"\x1b[38;5;{GRAD[idx]}m"
|
| 206 |
-
|
| 207 |
-
def fmt(v: int) -> str:
|
| 208 |
-
s = "." if (v == 0 and dot_for_zero) else str(v)
|
| 209 |
-
s = s.rjust(cell_w)
|
| 210 |
-
return color_code(v) + s + (RESET if colors else "")
|
| 211 |
-
|
| 212 |
-
def hline(left: str, mid: str, right: str) -> str:
|
| 213 |
-
return left + mid.join("\u2500" * cell_w for _ in range(size)) + right
|
| 214 |
-
|
| 215 |
-
rows = []
|
| 216 |
-
if border:
|
| 217 |
-
rows.append(hline("\u250c", "\u252c", "\u2510"))
|
| 218 |
-
for r in range(size):
|
| 219 |
-
content = "\u2502".join(fmt(v) for v in b[r])
|
| 220 |
-
rows.append(("\u2502" + content + "\u2502") if border else content)
|
| 221 |
-
if border:
|
| 222 |
-
rows.append(hline("\u2514" if r == size - 1 else "\u251c",
|
| 223 |
-
"\u2534" if r == size - 1 else "\u253c",
|
| 224 |
-
"\u2518" if r == size - 1 else "\u2524"))
|
| 225 |
-
return "\n".join(rows)
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
# =============================================================================
|
| 229 |
-
# CELL 10: Demonstrate stepping through the environment
|
| 230 |
-
# =============================================================================
|
| 231 |
-
# Action mapping: 0 = up, 1 = right, 2 = down, 3 = left
|
| 232 |
-
action = OpenSpielAction(action_id=0, game_name="2048")
|
| 233 |
-
result = openenv_process.step(action)
|
| 234 |
-
current_state = result.observation
|
| 235 |
-
print(render_board(current_state))
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
# =============================================================================
|
| 239 |
-
# CELL 11: RL Environment - Strategy execution with time limit
|
| 240 |
-
# =============================================================================
|
| 241 |
-
from typing import Callable
|
| 242 |
-
from unsloth import execute_with_time_limit
|
| 243 |
-
import itertools
|
| 244 |
-
|
| 245 |
-
def _execute_strategy(strategy, current_state: OpenSpielObservation):
|
| 246 |
-
"""
|
| 247 |
-
Execute a strategy function against the 2048 environment.
|
| 248 |
-
The strategy receives a board (list of lists) and returns an action int.
|
| 249 |
-
Runs until the game is done or the strategy fails.
|
| 250 |
-
Returns (steps, whether_2048_was_reached).
|
| 251 |
-
"""
|
| 252 |
-
assert callable(strategy)
|
| 253 |
-
|
| 254 |
-
steps = 0
|
| 255 |
-
total_reward = 0
|
| 256 |
-
while not current_state.done:
|
| 257 |
-
board, size = convert_to_board(current_state)
|
| 258 |
-
action = strategy(board)
|
| 259 |
-
try:
|
| 260 |
-
action = int(action)
|
| 261 |
-
except:
|
| 262 |
-
return steps, False
|
| 263 |
-
steps += 1
|
| 264 |
-
if type(action) is not int or action not in current_state.legal_actions:
|
| 265 |
-
return steps, max(itertools.chain.from_iterable(board)) == 2048
|
| 266 |
-
|
| 267 |
-
global port, openenv_process
|
| 268 |
-
port, openenv_process = launch_openenv(port, openenv_process)
|
| 269 |
-
action = OpenSpielAction(action_id=action, game_name="2048")
|
| 270 |
-
result = openenv_process.step(action)
|
| 271 |
-
current_state = result.observation
|
| 272 |
-
if result.reward is not None:
|
| 273 |
-
total_reward += result.reward
|
| 274 |
-
return steps, max(itertools.chain.from_iterable(board)) == 2048
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
# Time-limited wrapper (2 seconds default, later changed to 5)
|
| 278 |
-
@execute_with_time_limit(2)
|
| 279 |
-
def execute_strategy(strategy: Callable, current_state: OpenSpielObservation):
|
| 280 |
-
return _execute_strategy(strategy, current_state)
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
# =============================================================================
|
| 284 |
-
# CELL 12: Test with a trivial strategy
|
| 285 |
-
# =============================================================================
|
| 286 |
-
def always_move_left(board):
|
| 287 |
-
return 3
|
| 288 |
-
|
| 289 |
-
# Reset OpenEnv to an initial state!
|
| 290 |
-
port, openenv_process = launch_openenv(port, openenv_process)
|
| 291 |
-
result = openenv_process.reset()
|
| 292 |
-
current_state = result.observation
|
| 293 |
-
try:
|
| 294 |
-
steps, if_done = execute_strategy(always_move_left, current_state)
|
| 295 |
-
except TimeoutError as e:
|
| 296 |
-
print(f"Timed out with error = {str(e)}")
|
| 297 |
-
print(f"steps={steps}, if_done={if_done}")
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
# =============================================================================
|
| 301 |
-
# CELL 13: Extend time limit to 5 seconds for actual RL training
|
| 302 |
-
# =============================================================================
|
| 303 |
-
@execute_with_time_limit(5)
|
| 304 |
-
def execute_strategy(strategy: Callable, current_state: OpenSpielObservation):
|
| 305 |
-
return _execute_strategy(strategy, current_state)
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
# =============================================================================
|
| 309 |
-
# CELL 14: Code safety - check_python_modules (anti-reward-hacking)
|
| 310 |
-
# =============================================================================
|
| 311 |
-
from unsloth import check_python_modules
|
| 312 |
-
|
| 313 |
-
# Example: allowed (only stdlib)
|
| 314 |
-
sample_ok = """
|
| 315 |
-
def strategy(board):
|
| 316 |
-
import math
|
| 317 |
-
from typing import Callable
|
| 318 |
-
return "0"
|
| 319 |
-
"""
|
| 320 |
-
ok, info = check_python_modules(sample_ok)
|
| 321 |
-
print("Only Python imports?", ok) # True
|
| 322 |
-
print(info)
|
| 323 |
-
|
| 324 |
-
# Example: disallowed (numpy is non-stdlib)
|
| 325 |
-
sample_bad = """
|
| 326 |
-
def strategy(board):
|
| 327 |
-
from numpy import matmul
|
| 328 |
-
return "0"
|
| 329 |
-
"""
|
| 330 |
-
ok, info = check_python_modules(sample_bad)
|
| 331 |
-
print("Only Python imports?", ok) # False
|
| 332 |
-
print(info)
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
# =============================================================================
|
| 336 |
-
# CELL 15: Sandboxed function execution (no global variable leakage)
|
| 337 |
-
# =============================================================================
|
| 338 |
-
from unsloth import create_locked_down_function
|
| 339 |
-
|
| 340 |
-
# This will fail - np is not defined inside the sandbox
|
| 341 |
-
function_bad = """
|
| 342 |
-
def import_numpy():
|
| 343 |
-
np.matmul
|
| 344 |
-
print("Success")
|
| 345 |
-
"""
|
| 346 |
-
f = create_locked_down_function(function_bad)
|
| 347 |
-
try:
|
| 348 |
-
f()
|
| 349 |
-
except Exception as e:
|
| 350 |
-
print(str(e)) # "name 'np' is not defined"
|
| 351 |
-
|
| 352 |
-
# This will work - no external references
|
| 353 |
-
function_good = """
|
| 354 |
-
def add(a, b):
|
| 355 |
-
def adder(a):
|
| 356 |
-
return a + b
|
| 357 |
-
return adder(b) + b
|
| 358 |
-
"""
|
| 359 |
-
f = create_locked_down_function(function_good)
|
| 360 |
-
try:
|
| 361 |
-
print(f(10, 20)) # 60
|
| 362 |
-
except Exception as e:
|
| 363 |
-
print(str(e))
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
# =============================================================================
|
| 367 |
-
# CELL 16: THE PROMPT - How the LLM interacts with the environment
|
| 368 |
-
# =============================================================================
|
| 369 |
-
prompt = """
|
| 370 |
-
Create a new short 2048 strategy using only native Python code.
|
| 371 |
-
You are given a list of list of numbers for the current board state.
|
| 372 |
-
Output one action for "0", "1", "2", "3" on what is the optimal next step.
|
| 373 |
-
Output your new short function in backticks using the format below:
|
| 374 |
-
```python
|
| 375 |
-
def strategy(board):
|
| 376 |
-
return "0" # Example
|
| 377 |
-
```
|
| 378 |
-
All helper functions should be inside def strategy. Only output the short function `strategy`.
|
| 379 |
-
""".strip()
|
| 380 |
-
print(prompt)
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
# =============================================================================
|
| 384 |
-
# CELL 17: Test inference before RL training
|
| 385 |
-
# =============================================================================
|
| 386 |
-
text = tokenizer.apply_chat_template(
|
| 387 |
-
[{"role": "user", "content": prompt}],
|
| 388 |
-
tokenize = False,
|
| 389 |
-
add_generation_prompt = True,
|
| 390 |
-
reasoning_effort = "low",
|
| 391 |
-
)
|
| 392 |
-
|
| 393 |
-
from transformers import TextStreamer
|
| 394 |
-
_ = model.generate(
|
| 395 |
-
**tokenizer(text, return_tensors="pt").to("cuda"),
|
| 396 |
-
temperature = 1.0,
|
| 397 |
-
max_new_tokens = 512,
|
| 398 |
-
streamer = TextStreamer(tokenizer, skip_prompt=False),
|
| 399 |
-
)
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
# =============================================================================
|
| 403 |
-
# CELL 18: REWARD FUNCTION 1 - extract_function (helper)
|
| 404 |
-
# =============================================================================
|
| 405 |
-
def extract_function(text):
|
| 406 |
-
"""
|
| 407 |
-
Extract a Python function wrapped in triple backticks from the LLM response.
|
| 408 |
-
Returns the function source string, or None if not found.
|
| 409 |
-
"""
|
| 410 |
-
if text.count("```") >= 2:
|
| 411 |
-
first = text.find("```") + 3
|
| 412 |
-
second = text.find("```", first)
|
| 413 |
-
fx = text[first:second].strip()
|
| 414 |
-
fx = fx.removeprefix("python\n")
|
| 415 |
-
fx = fx[fx.find("def"):]
|
| 416 |
-
if fx.startswith("def strategy(board):"):
|
| 417 |
-
return fx
|
| 418 |
-
return None
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
# =============================================================================
|
| 422 |
-
# CELL 19: REWARD FUNCTION 2 - function_works
|
| 423 |
-
# =============================================================================
|
| 424 |
-
def function_works(completions, **kwargs):
|
| 425 |
-
"""
|
| 426 |
-
Reward: Does the generated code parse as valid Python and compile?
|
| 427 |
-
+1.0 if valid function that can be created
|
| 428 |
-
-0.5 if it has the right structure but exec fails
|
| 429 |
-
-2.0 if no function extracted or syntax error
|
| 430 |
-
"""
|
| 431 |
-
scores = []
|
| 432 |
-
for completion in completions:
|
| 433 |
-
score = 0
|
| 434 |
-
response = completion[0]["content"]
|
| 435 |
-
function = extract_function(response)
|
| 436 |
-
if function is not None:
|
| 437 |
-
ok, info = check_python_modules(function)
|
| 438 |
-
if function is None or "error" in info:
|
| 439 |
-
score = -2.0
|
| 440 |
-
else:
|
| 441 |
-
try:
|
| 442 |
-
new_strategy = create_locked_down_function(function)
|
| 443 |
-
score = 1.0
|
| 444 |
-
except:
|
| 445 |
-
score = -0.5
|
| 446 |
-
scores.append(score)
|
| 447 |
-
return scores
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
# =============================================================================
|
| 451 |
-
# CELL 20: REWARD FUNCTION 3 - no_cheating
|
| 452 |
-
# =============================================================================
|
| 453 |
-
def no_cheating(completions, **kwargs):
|
| 454 |
-
"""
|
| 455 |
-
Reward: Does the function only use Python stdlib imports?
|
| 456 |
-
+1.0 if only stdlib imports
|
| 457 |
-
-20.0 if non-stdlib imports (heavy penalty!)
|
| 458 |
-
-1.0 if function extraction failed
|
| 459 |
-
"""
|
| 460 |
-
scores = []
|
| 461 |
-
for completion in completions:
|
| 462 |
-
score = 0
|
| 463 |
-
response = completion[0]["content"]
|
| 464 |
-
function = extract_function(response)
|
| 465 |
-
if function is not None:
|
| 466 |
-
ok, info = check_python_modules(function)
|
| 467 |
-
scores.append(1.0 if ok else -20.0) # Penalize heavily!
|
| 468 |
-
else:
|
| 469 |
-
scores.append(-1.0) # Failed creating function
|
| 470 |
-
return scores
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
# =============================================================================
|
| 474 |
-
# CELL 21: REWARD FUNCTION 4 - strategy_succeeds
|
| 475 |
-
# =============================================================================
|
| 476 |
-
import numpy as np
|
| 477 |
-
|
| 478 |
-
global PRINTER
|
| 479 |
-
PRINTER = 0
|
| 480 |
-
|
| 481 |
-
def strategy_succeeds(completions, **kwargs):
|
| 482 |
-
"""
|
| 483 |
-
Reward: Does the strategy actually play 2048 successfully?
|
| 484 |
-
+20.0 if the strategy reaches 2048 (massive reward!)
|
| 485 |
-
+2.0 if the function runs and plays but doesn't reach 2048
|
| 486 |
-
-1.0 if timeout (strategy takes too long)
|
| 487 |
-
-3.0 if exception during execution
|
| 488 |
-
0 if function is broken/can't be created
|
| 489 |
-
"""
|
| 490 |
-
global PRINTER
|
| 491 |
-
scores = []
|
| 492 |
-
for completion in completions:
|
| 493 |
-
printed = False
|
| 494 |
-
score = 0
|
| 495 |
-
response = completion[0]["content"]
|
| 496 |
-
function = extract_function(response)
|
| 497 |
-
if PRINTER % 5 == 0:
|
| 498 |
-
printed = True
|
| 499 |
-
print(function)
|
| 500 |
-
PRINTER += 1
|
| 501 |
-
if function is not None:
|
| 502 |
-
ok, info = check_python_modules(function)
|
| 503 |
-
if function is None or "error" in info:
|
| 504 |
-
scores.append(0)
|
| 505 |
-
continue
|
| 506 |
-
try:
|
| 507 |
-
new_strategy = create_locked_down_function(function)
|
| 508 |
-
except:
|
| 509 |
-
scores.append(0)
|
| 510 |
-
continue
|
| 511 |
-
try:
|
| 512 |
-
# Reset OpenEnv to an initial state!
|
| 513 |
-
global port, openenv_process
|
| 514 |
-
port, openenv_process = launch_openenv(port, openenv_process)
|
| 515 |
-
result = openenv_process.reset()
|
| 516 |
-
current_state = result.observation
|
| 517 |
-
steps, if_done = execute_strategy(new_strategy, current_state)
|
| 518 |
-
print(f"Steps = {steps} If Done = {if_done}")
|
| 519 |
-
if printed is False:
|
| 520 |
-
print(function)
|
| 521 |
-
print(render_board(current_state))
|
| 522 |
-
if if_done:
|
| 523 |
-
scores.append(20.0) # Success - massively reward!
|
| 524 |
-
else:
|
| 525 |
-
scores.append(2.0) # Failed but function works!
|
| 526 |
-
except TimeoutError as e:
|
| 527 |
-
print("Timeout")
|
| 528 |
-
scores.append(-1.0) # Failed with timeout
|
| 529 |
-
except Exception as e:
|
| 530 |
-
print(f"Exception = {str(e)}")
|
| 531 |
-
scores.append(-3.0) # Failed
|
| 532 |
-
return scores
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
# =============================================================================
|
| 536 |
-
# CELL 22: Create the dataset (replicated prompt)
|
| 537 |
-
# =============================================================================
|
| 538 |
-
from datasets import Dataset
|
| 539 |
-
|
| 540 |
-
dataset = Dataset.from_list([
|
| 541 |
-
{
|
| 542 |
-
"prompt": [{"role": "user", "content": prompt.strip()}],
|
| 543 |
-
"answer": 0,
|
| 544 |
-
"reasoning_effort": "low",
|
| 545 |
-
}
|
| 546 |
-
] * 1000)
|
| 547 |
-
|
| 548 |
-
maximum_length = len(tokenizer.apply_chat_template(
|
| 549 |
-
[{"role": "user", "content": prompt.strip()}],
|
| 550 |
-
add_generation_prompt=True,
|
| 551 |
-
))
|
| 552 |
-
print(f"Prompt token length: {maximum_length}")
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
# =============================================================================
|
| 556 |
-
# CELL 23: GRPO Training Configuration
|
| 557 |
-
# =============================================================================
|
| 558 |
-
max_prompt_length = maximum_length + 1 # + 1 just in case!
|
| 559 |
-
max_completion_length = max_seq_length - max_prompt_length
|
| 560 |
-
|
| 561 |
-
from trl import GRPOConfig, GRPOTrainer
|
| 562 |
-
|
| 563 |
-
training_args = GRPOConfig(
|
| 564 |
-
temperature = 1.0,
|
| 565 |
-
learning_rate = 2e-4,
|
| 566 |
-
weight_decay = 0.001,
|
| 567 |
-
warmup_ratio = 0.1,
|
| 568 |
-
lr_scheduler_type = "linear",
|
| 569 |
-
optim = "adamw_8bit",
|
| 570 |
-
logging_steps = 1,
|
| 571 |
-
per_device_train_batch_size = 1,
|
| 572 |
-
gradient_accumulation_steps = 1, # Increase to 4 for smoother training
|
| 573 |
-
num_generations = 2, # Decrease if out of memory
|
| 574 |
-
max_prompt_length = max_prompt_length,
|
| 575 |
-
max_completion_length = max_completion_length,
|
| 576 |
-
# num_train_epochs = 1, # Set to 1 for a full training run
|
| 577 |
-
max_steps = 600,
|
| 578 |
-
save_steps = 100,
|
| 579 |
-
report_to = "trackio", # Can use Weights & Biases, TrackIO
|
| 580 |
-
output_dir = "outputs",
|
| 581 |
-
)
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
# =============================================================================
|
| 585 |
-
# CELL 24: Create GRPO Trainer and Train
|
| 586 |
-
# =============================================================================
|
| 587 |
-
trainer = GRPOTrainer(
|
| 588 |
-
model = model,
|
| 589 |
-
processing_class = tokenizer,
|
| 590 |
-
reward_funcs = [
|
| 591 |
-
function_works, # Reward 1: Is it valid Python?
|
| 592 |
-
no_cheating, # Reward 2: Only stdlib imports?
|
| 593 |
-
strategy_succeeds, # Reward 3: Does it actually play 2048?
|
| 594 |
-
],
|
| 595 |
-
args = training_args,
|
| 596 |
-
train_dataset = dataset,
|
| 597 |
-
)
|
| 598 |
-
|
| 599 |
-
# Start training! (~5 hours for 600 steps on T4)
|
| 600 |
-
# Expect 0 reward for ~first 100 steps, then gradual improvement
|
| 601 |
-
trainer.train()
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
# =============================================================================
|
| 605 |
-
# CELL 25: Inference after RL training
|
| 606 |
-
# =============================================================================
|
| 607 |
-
text = tokenizer.apply_chat_template(
|
| 608 |
-
[{"role": "user", "content": prompt}],
|
| 609 |
-
tokenize = False,
|
| 610 |
-
add_generation_prompt = True,
|
| 611 |
-
reasoning_effort = "low",
|
| 612 |
-
)
|
| 613 |
-
|
| 614 |
-
from transformers import TextStreamer
|
| 615 |
-
_ = model.generate(
|
| 616 |
-
**tokenizer(text, return_tensors="pt").to("cuda"),
|
| 617 |
-
temperature = 1.0,
|
| 618 |
-
max_new_tokens = 1024,
|
| 619 |
-
streamer = TextStreamer(tokenizer, skip_prompt=False),
|
| 620 |
-
)
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
# =============================================================================
|
| 624 |
-
# CELL 26: Save the model (optional)
|
| 625 |
-
# =============================================================================
|
| 626 |
-
# Merge and push to hub in mxfp4 4bit format
|
| 627 |
-
if False:
|
| 628 |
-
model.save_pretrained_merged("finetuned_model", tokenizer, save_method="mxfp4")
|
| 629 |
-
if False:
|
| 630 |
-
model.push_to_hub_merged("repo_id/repo_name", tokenizer, token="hf...", save_method="mxfp4")
|
| 631 |
-
|
| 632 |
-
# Merge and push to hub in 16bit
|
| 633 |
-
if False:
|
| 634 |
-
model.save_pretrained_merged("finetuned_model", tokenizer, save_method="merged_16bit")
|
| 635 |
-
if False:
|
| 636 |
-
model.push_to_hub_merged("hf/gpt-oss-finetune", tokenizer, save_method="merged_16bit", token="")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/openenv/2048_pattern.md
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
# 2048 Example — Code-as-Policy Pattern
|
| 2 |
-
|
| 3 |
-
**Full code**: [2048_example.py](./2048_example.py)
|
| 4 |
-
|
| 5 |
-
## Key Insight: LLM Writes Code, Not Moves
|
| 6 |
-
|
| 7 |
-
The LLM does NOT play move-by-move. Instead:
|
| 8 |
-
1. LLM receives a prompt asking it to write a `strategy(board)` function
|
| 9 |
-
2. LLM generates Python code wrapped in triple backticks
|
| 10 |
-
3. Code is extracted, sandboxed (no global access), and executed against the live game
|
| 11 |
-
4. Reward is based on whether the strategy works
|
| 12 |
-
|
| 13 |
-
This is **"code-as-policy"** — the LLM generates an algorithm, not individual actions.
|
| 14 |
-
|
| 15 |
-
## The Prompt
|
| 16 |
-
|
| 17 |
-
```
|
| 18 |
-
Create a new short 2048 strategy using only native Python code.
|
| 19 |
-
You are given a list of list of numbers for the current board state.
|
| 20 |
-
Output one action for "0", "1", "2", "3" on what is the optimal next step.
|
| 21 |
-
Output your new short function in backticks using the format below:
|
| 22 |
-
```python
|
| 23 |
-
def strategy(board):
|
| 24 |
-
return "0" # Example
|
| 25 |
-
```
|
| 26 |
-
All helper functions should be inside def strategy. Only output the short function `strategy`.
|
| 27 |
-
```
|
| 28 |
-
|
| 29 |
-
## Three Reward Functions
|
| 30 |
-
|
| 31 |
-
| Function | Score | Condition |
|
| 32 |
-
|---|---|---|
|
| 33 |
-
| `function_works` | +1.0 | Valid Python that compiles |
|
| 34 |
-
| | -0.5 | Right structure but exec fails |
|
| 35 |
-
| | -2.0 | No function / syntax error |
|
| 36 |
-
| `no_cheating` | +1.0 | Only stdlib imports |
|
| 37 |
-
| | -20.0 | Non-stdlib imports |
|
| 38 |
-
| `strategy_succeeds` | +20.0 | Reaches tile 2048 |
|
| 39 |
-
| | +2.0 | Runs but doesn't win |
|
| 40 |
-
| | -1.0 | Timeout (>5 sec) |
|
| 41 |
-
| | -3.0 | Exception |
|
| 42 |
-
|
| 43 |
-
## Training Setup
|
| 44 |
-
|
| 45 |
-
- Model: `unsloth/gpt-oss-20b` with LoRA (r=4)
|
| 46 |
-
- Trainer: `trl.GRPOTrainer` with `trl.GRPOConfig`
|
| 47 |
-
- Dataset: 1000 copies of the same prompt (diversity from temperature=1.0)
|
| 48 |
-
- `num_generations=2`, `max_steps=600`, `lr=2e-4`, `optim=adamw_8bit`
|
| 49 |
-
- ~5 hours on T4, rewards start appearing after ~100 steps
|
| 50 |
-
|
| 51 |
-
## OpenEnv-Specific Patterns
|
| 52 |
-
|
| 53 |
-
```python
|
| 54 |
-
# Launch environment server
|
| 55 |
-
from unsloth import launch_openenv
|
| 56 |
-
launch_openenv = functools.partial(
|
| 57 |
-
launch_openenv,
|
| 58 |
-
working_directory=working_directory,
|
| 59 |
-
server="envs.openspiel_env.server.app:app",
|
| 60 |
-
environment={**os.environ, "OPENSPIEL_GAME": "2048", ...},
|
| 61 |
-
openenv_class=OpenSpielEnv,
|
| 62 |
-
)
|
| 63 |
-
|
| 64 |
-
# Reset and step
|
| 65 |
-
port, openenv_process = launch_openenv(port, openenv_process)
|
| 66 |
-
result = openenv_process.reset()
|
| 67 |
-
result = openenv_process.step(OpenSpielAction(action_id=0, game_name="2048"))
|
| 68 |
-
```
|
| 69 |
-
|
| 70 |
-
## Safety Utilities (from Unsloth)
|
| 71 |
-
|
| 72 |
-
- `check_python_modules(code)` — returns (ok, info); ok=True if only stdlib imports
|
| 73 |
-
- `create_locked_down_function(code)` — sandboxed exec, no global variable leakage
|
| 74 |
-
- `execute_with_time_limit(seconds)` — decorator for timeout enforcement
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/openenv/overview.md
DELETED
|
@@ -1,141 +0,0 @@
|
|
| 1 |
-
# OpenEnv Framework (Meta) — What We Need to Know
|
| 2 |
-
|
| 3 |
-
**Repo**: https://github.com/meta-pytorch/OpenEnv
|
| 4 |
-
**Version**: 0.2.1 stable (required by hackathon), 0.2.2.dev0 in dev
|
| 5 |
-
**License**: BSD 3-Clause | **Python**: 3.10+
|
| 6 |
-
|
| 7 |
-
## Architecture
|
| 8 |
-
|
| 9 |
-
Client-server over WebSocket. Environment runs inside Docker as a FastAPI server.
|
| 10 |
-
|
| 11 |
-
```
|
| 12 |
-
LLM/Agent <--> EnvClient (WebSocket) <--> Environment (FastAPI server in Docker)
|
| 13 |
-
```
|
| 14 |
-
|
| 15 |
-
## Core Types (from `openenv.core.env_server.types`)
|
| 16 |
-
|
| 17 |
-
```python
|
| 18 |
-
class Action(BaseModel):
|
| 19 |
-
metadata: Dict[str, Any] = Field(default_factory=dict)
|
| 20 |
-
|
| 21 |
-
class Observation(BaseModel):
|
| 22 |
-
done: bool = False
|
| 23 |
-
reward: bool | int | float | None = None
|
| 24 |
-
metadata: Dict[str, Any] = Field(default_factory=dict)
|
| 25 |
-
|
| 26 |
-
class State(BaseModel):
|
| 27 |
-
episode_id: Optional[str] = None
|
| 28 |
-
step_count: int = 0
|
| 29 |
-
```
|
| 30 |
-
|
| 31 |
-
## Creating a Custom Environment (Server Side)
|
| 32 |
-
|
| 33 |
-
Subclass `Environment[ActT, ObsT, StateT]`:
|
| 34 |
-
|
| 35 |
-
```python
|
| 36 |
-
from openenv.core.env_server.interfaces import Environment
|
| 37 |
-
from openenv.core.env_server.types import State
|
| 38 |
-
|
| 39 |
-
class MyEnvironment(Environment[MyAction, MyObservation, MyState]):
|
| 40 |
-
SUPPORTS_CONCURRENT_SESSIONS = True
|
| 41 |
-
|
| 42 |
-
def reset(self, seed=None, episode_id=None, **kwargs) -> ObsT:
|
| 43 |
-
"""Initialize episode, return initial observation."""
|
| 44 |
-
|
| 45 |
-
def step(self, action: ActT, timeout_s=None, **kwargs) -> ObsT:
|
| 46 |
-
"""Execute action, return observation (with .done and .reward set)."""
|
| 47 |
-
|
| 48 |
-
@property
|
| 49 |
-
def state(self) -> StateT:
|
| 50 |
-
"""Return current episode state."""
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
## Creating a Custom Client
|
| 54 |
-
|
| 55 |
-
Subclass `EnvClient[ActT, ObsT, StateT]`:
|
| 56 |
-
|
| 57 |
-
```python
|
| 58 |
-
from openenv.core.env_client import EnvClient
|
| 59 |
-
|
| 60 |
-
class MyEnvClient(EnvClient[MyAction, MyObservation, MyState]):
|
| 61 |
-
def _step_payload(self, action: ActT) -> Dict:
|
| 62 |
-
"""Convert action to JSON dict for wire transmission."""
|
| 63 |
-
|
| 64 |
-
def _parse_result(self, payload: Dict) -> StepResult[ObsT]:
|
| 65 |
-
"""Parse server response into StepResult."""
|
| 66 |
-
|
| 67 |
-
def _parse_state(self, payload: Dict) -> StateT:
|
| 68 |
-
"""Parse state response into your State type."""
|
| 69 |
-
```
|
| 70 |
-
|
| 71 |
-
## Project Structure (`openenv init my_env`)
|
| 72 |
-
|
| 73 |
-
```
|
| 74 |
-
my_env/
|
| 75 |
-
__init__.py
|
| 76 |
-
models.py # Action, Observation, State subclasses
|
| 77 |
-
client.py # EnvClient subclass
|
| 78 |
-
openenv.yaml # Manifest
|
| 79 |
-
pyproject.toml # deps: "openenv-core[core]>=0.2.1"
|
| 80 |
-
server/
|
| 81 |
-
__init__.py
|
| 82 |
-
my_env_environment.py # Environment subclass
|
| 83 |
-
app.py # create_app() call
|
| 84 |
-
Dockerfile
|
| 85 |
-
requirements.txt
|
| 86 |
-
```
|
| 87 |
-
|
| 88 |
-
## openenv.yaml Manifest
|
| 89 |
-
|
| 90 |
-
```yaml
|
| 91 |
-
spec_version: 1
|
| 92 |
-
name: my_env
|
| 93 |
-
type: space
|
| 94 |
-
runtime: fastapi
|
| 95 |
-
app: server.app:app
|
| 96 |
-
port: 8000
|
| 97 |
-
```
|
| 98 |
-
|
| 99 |
-
## Server App Entry Point
|
| 100 |
-
|
| 101 |
-
```python
|
| 102 |
-
from openenv.core.env_server.http_server import create_app
|
| 103 |
-
app = create_app(MyEnvironment, MyAction, MyObservation, env_name="my_env")
|
| 104 |
-
```
|
| 105 |
-
|
| 106 |
-
## Deploy to HF Spaces
|
| 107 |
-
|
| 108 |
-
```bash
|
| 109 |
-
openenv push [--repo-id username/my_env] [--private]
|
| 110 |
-
```
|
| 111 |
-
|
| 112 |
-
Dockerfile uses `ghcr.io/meta-pytorch/openenv-base:latest`, runs `uvicorn server.app:app --host 0.0.0.0 --port 8000`.
|
| 113 |
-
|
| 114 |
-
## Concrete Example: Chess Environment
|
| 115 |
-
|
| 116 |
-
```python
|
| 117 |
-
class ChessAction(Action):
|
| 118 |
-
move: str # UCI format "e2e4"
|
| 119 |
-
|
| 120 |
-
class ChessObservation(Observation):
|
| 121 |
-
fen: str = ""
|
| 122 |
-
legal_moves: List[str] = []
|
| 123 |
-
is_check: bool = False
|
| 124 |
-
result: Optional[str] = None # "1-0", "0-1", "1/2-1/2"
|
| 125 |
-
|
| 126 |
-
class ChessState(State):
|
| 127 |
-
fen: str = "rnbqkbnr/..."
|
| 128 |
-
current_player: str = "white"
|
| 129 |
-
move_history: List[str] = []
|
| 130 |
-
```
|
| 131 |
-
|
| 132 |
-
Reward: +1.0 win, -1.0 loss, 0.0 draw, -0.1 invalid move.
|
| 133 |
-
|
| 134 |
-
## Integration with GRPO Training
|
| 135 |
-
|
| 136 |
-
From `examples/grpo_blackjack/`:
|
| 137 |
-
1. `play_game()` — orchestrates env.reset() / env.step() loop
|
| 138 |
-
2. `format_prompt()` — converts game state to LLM prompt
|
| 139 |
-
3. `parse_action()` — extracts actions from LLM text
|
| 140 |
-
4. `simple_grpo_loss()` — GRPO with KL penalty
|
| 141 |
-
5. Compatible with: **TRL**, **Unsloth**, **SkyRL**, **ART**, **Oumi**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/existing_work.md
DELETED
|
@@ -1,51 +0,0 @@
|
|
| 1 |
-
# Existing RL & ML Work on Origami
|
| 2 |
-
|
| 3 |
-
## "Automating Rigid Origami Design" — IJCAI 2023 (Most Relevant)
|
| 4 |
-
|
| 5 |
-
- **Paper**: https://www.ijcai.org/proceedings/2023/0645.pdf
|
| 6 |
-
- **Code**: https://github.com/belalugaX/rigid-origami
|
| 7 |
-
|
| 8 |
-
**RL Formulation:**
|
| 9 |
-
- Environment = board game on a grid
|
| 10 |
-
- State = current vertex graph forming crease pattern
|
| 11 |
-
- Actions = place vertices on grid (with action masking for symmetry)
|
| 12 |
-
- Reward = sparse, based on objective (shape approximation, packaging)
|
| 13 |
-
- Validation = triangle-triangle intersection + kinematic check
|
| 14 |
-
|
| 15 |
-
**Algorithms tested:** Random, BFS, DFS, MCTS, Evolutionary, PPO
|
| 16 |
-
**Finding:** Action space grows exponentially. Symmetry masking is critical.
|
| 17 |
-
|
| 18 |
-
## ML for Origami Inverse Design — Nature 2022
|
| 19 |
-
|
| 20 |
-
- Decision tree / random forest for inverse design
|
| 21 |
-
- Given desired mechanical properties → predict crease pattern parameters
|
| 22 |
-
- Relevant for: how to define reward functions around structural properties
|
| 23 |
-
|
| 24 |
-
## UCLA Robotic Origami (2023)
|
| 25 |
-
|
| 26 |
-
- Deep learning for robotic paper folding
|
| 27 |
-
- RL for physical execution of folds (robot arm)
|
| 28 |
-
- Different scope (physical manipulation, not pattern design)
|
| 29 |
-
|
| 30 |
-
## Key Papers
|
| 31 |
-
|
| 32 |
-
| Paper | Year | Key Contribution |
|
| 33 |
-
|-------|------|-----------------|
|
| 34 |
-
| Bern & Hayes | 1996 | Global flat-foldability is NP-complete |
|
| 35 |
-
| Lang, "Computational origami" | 1996 | TreeMaker algorithm |
|
| 36 |
-
| Tachi & Demaine, "Origamizer" | 2017 | Universal folding algorithm |
|
| 37 |
-
| Ghassaei et al. | 2018 | GPU truss model simulation |
|
| 38 |
-
| Lang, "Additive algorithm" | 2021 | Scalable local origami design |
|
| 39 |
-
| Nature, "Algorithmic origami" | 2022 | Optimization frameworks |
|
| 40 |
-
| IJCAI, "Automating Rigid Origami" | 2023 | RL for origami design |
|
| 41 |
-
|
| 42 |
-
## Origami AI Roadmap (Community)
|
| 43 |
-
|
| 44 |
-
Source: https://origami.kosmulski.org/blog/2023-01-12-origami-ai-roadmap
|
| 45 |
-
|
| 46 |
-
Key insight — a minimal AI origami system needs:
|
| 47 |
-
1. Intent → stick model converter (**the hard/missing part**)
|
| 48 |
-
2. Stick model → crease pattern (TreeMaker exists)
|
| 49 |
-
3. CP simulation/visualization (Ghassaei exists)
|
| 50 |
-
|
| 51 |
-
Training data scarcity is a major challenge.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/fold_format.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
| 1 |
-
# FOLD File Format — Our State Representation
|
| 2 |
-
|
| 3 |
-
**Spec**: https://edemaine.github.io/fold/doc/spec.html (v1.2)
|
| 4 |
-
**Format**: JSON | **GitHub**: https://github.com/edemaine/fold
|
| 5 |
-
|
| 6 |
-
The standard interchange format for computational origami. We use this as our internal state.
|
| 7 |
-
|
| 8 |
-
## Key Fields
|
| 9 |
-
|
| 10 |
-
**Vertices:**
|
| 11 |
-
- `vertices_coords`: `[[x,y], [x,y,z], ...]`
|
| 12 |
-
|
| 13 |
-
**Edges:**
|
| 14 |
-
- `edges_vertices`: `[[u,v], ...]` — endpoint pairs
|
| 15 |
-
- `edges_assignment`: fold type per edge:
|
| 16 |
-
- `"M"` Mountain | `"V"` Valley | `"B"` Boundary | `"F"` Flat | `"U"` Unassigned
|
| 17 |
-
- `edges_foldAngle`: degrees [-180, 180]; positive=valley, negative=mountain, 0=flat
|
| 18 |
-
|
| 19 |
-
**Faces:**
|
| 20 |
-
- `faces_vertices`: vertex IDs in counterclockwise order
|
| 21 |
-
|
| 22 |
-
**Layer Ordering:**
|
| 23 |
-
- `faceOrders`: `[f, g, s]` triples — s=+1 (f above g), s=-1 (f below g)
|
| 24 |
-
|
| 25 |
-
**Metadata:**
|
| 26 |
-
- `frame_classes`: `"creasePattern"`, `"foldedForm"`, `"graph"`
|
| 27 |
-
- `frame_unit`: `"unit"`, `"mm"`, `"cm"`, `"m"`
|
| 28 |
-
|
| 29 |
-
## Python Usage
|
| 30 |
-
|
| 31 |
-
```python
|
| 32 |
-
import json
|
| 33 |
-
|
| 34 |
-
with open("model.fold") as f:
|
| 35 |
-
data = json.load(f)
|
| 36 |
-
|
| 37 |
-
vertices = data["vertices_coords"]
|
| 38 |
-
edges = data["edges_vertices"]
|
| 39 |
-
assignments = data["edges_assignment"] # ["M", "V", "B", ...]
|
| 40 |
-
fold_angles = data.get("edges_foldAngle") # [-180, 180, 0, ...]
|
| 41 |
-
faces = data["faces_vertices"]
|
| 42 |
-
```
|
| 43 |
-
|
| 44 |
-
## Why It Matters
|
| 45 |
-
- Lingua franca of all origami software
|
| 46 |
-
- JSON = trivially parseable, serializable for replay buffers
|
| 47 |
-
- Encodes everything: geometry, fold types, angles, layer ordering
|
| 48 |
-
- Both Ghassaei's simulator and PyOri support it
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/fold_types_deep.md
DELETED
|
@@ -1,997 +0,0 @@
|
|
| 1 |
-
# Origami Fold Types, Bases, and Operations: Complete Action Space for RL Environment
|
| 2 |
-
|
| 3 |
-
> Deep research compilation for building an origami reinforcement learning environment.
|
| 4 |
-
> Covers: primitive operations, fold taxonomy, bases, crane sequence, tessellation patterns,
|
| 5 |
-
> Huzita-Justin axioms, and complexity analysis.
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## Table of Contents
|
| 10 |
-
|
| 11 |
-
1. [Huzita-Justin Axioms (The Primitive Action Space)](#1-huzita-justin-axioms---the-primitive-action-space)
|
| 12 |
-
2. [All Fold Types (Compound Operations)](#2-all-fold-types---compound-operations)
|
| 13 |
-
3. [Origami Bases (Starting Configurations)](#3-origami-bases---starting-configurations)
|
| 14 |
-
4. [Crane (Tsuru) Fold Sequence](#4-crane-tsuru-complete-fold-sequence)
|
| 15 |
-
5. [Compression/Packing Origami Patterns](#5-compressionpacking-origami-patterns)
|
| 16 |
-
6. [Yoshizawa-Randlett Notation System](#6-yoshizawa-randlett-notation-system)
|
| 17 |
-
7. [Fold Sequence Complexity](#7-fold-sequence-complexity)
|
| 18 |
-
8. [Flat-Foldability Theorems](#8-flat-foldability-theorems)
|
| 19 |
-
9. [RL Environment Design Implications](#9-rl-environment-design-implications)
|
| 20 |
-
|
| 21 |
-
---
|
| 22 |
-
|
| 23 |
-
## 1. Huzita-Justin Axioms -- The Primitive Action Space
|
| 24 |
-
|
| 25 |
-
These 7 axioms define ALL possible single-fold operations. Any single fold you can make on a
|
| 26 |
-
piece of paper corresponds to exactly one of these axioms. They are the **true primitive
|
| 27 |
-
action space** for origami.
|
| 28 |
-
|
| 29 |
-
First discovered by Jacques Justin (1986), rediscovered by Humiaki Huzita (1991), with Axiom 7
|
| 30 |
-
found by Koshiro Hatori (2001) and Robert J. Lang independently.
|
| 31 |
-
|
| 32 |
-
### Axiom 1: Fold Through Two Points
|
| 33 |
-
- **Input**: Two distinct points p1 and p2
|
| 34 |
-
- **Output**: A unique fold (crease line) that passes through both points
|
| 35 |
-
- **Geometric equivalent**: Drawing a line through two points (straightedge operation)
|
| 36 |
-
- **Solutions**: Exactly 1
|
| 37 |
-
- **Parameters**: `(p1_x, p1_y, p2_x, p2_y)`
|
| 38 |
-
|
| 39 |
-
### Axiom 2: Point-to-Point Fold
|
| 40 |
-
- **Input**: Two distinct points p1 and p2
|
| 41 |
-
- **Output**: A unique fold that places p1 onto p2
|
| 42 |
-
- **Geometric equivalent**: Constructing the perpendicular bisector of the segment p1-p2
|
| 43 |
-
- **Solutions**: Exactly 1
|
| 44 |
-
- **Parameters**: `(p1_x, p1_y, p2_x, p2_y)`
|
| 45 |
-
|
| 46 |
-
### Axiom 3: Line-to-Line Fold
|
| 47 |
-
- **Input**: Two lines l1 and l2
|
| 48 |
-
- **Output**: A fold that places l1 onto l2
|
| 49 |
-
- **Geometric equivalent**: Bisecting the angle between two lines
|
| 50 |
-
- **Solutions**: 1 if lines are parallel (perpendicular bisector), 2 if lines intersect (two angle bisectors)
|
| 51 |
-
- **Parameters**: `(l1_point, l1_direction, l2_point, l2_direction)`
|
| 52 |
-
|
| 53 |
-
### Axiom 4: Fold Through Point Perpendicular to Line
|
| 54 |
-
- **Input**: A point p1 and a line l1
|
| 55 |
-
- **Output**: A unique fold perpendicular to l1 that passes through p1
|
| 56 |
-
- **Geometric equivalent**: Constructing a perpendicular through a point
|
| 57 |
-
- **Solutions**: Exactly 1
|
| 58 |
-
- **Parameters**: `(p1_x, p1_y, l1_point, l1_direction)`
|
| 59 |
-
|
| 60 |
-
### Axiom 5: Point-to-Line Through Point
|
| 61 |
-
- **Input**: Two points p1, p2 and a line l1
|
| 62 |
-
- **Output**: A fold that places p1 onto l1 and passes through p2
|
| 63 |
-
- **Geometric equivalent**: Finding a tangent to a parabola from an external point (solves quadratic)
|
| 64 |
-
- **Solutions**: 0, 1, or 2
|
| 65 |
-
- **Parameters**: `(p1_x, p1_y, p2_x, p2_y, l1_point, l1_direction)`
|
| 66 |
-
|
| 67 |
-
### Axiom 6: Two Points onto Two Lines (The Beloch Fold)
|
| 68 |
-
- **Input**: Two points p1, p2 and two lines l1, l2
|
| 69 |
-
- **Output**: A fold that simultaneously places p1 onto l1 AND p2 onto l2
|
| 70 |
-
- **Geometric equivalent**: Finding a common tangent to two parabolas (solves cubic equations!)
|
| 71 |
-
- **Solutions**: 0, 1, 2, or 3
|
| 72 |
-
- **Parameters**: `(p1_x, p1_y, p2_x, p2_y, l1_point, l1_direction, l2_point, l2_direction)`
|
| 73 |
-
- **Significance**: This is what gives origami more power than compass-and-straightedge. It can
|
| 74 |
-
trisect angles and double cubes (both impossible with compass/straightedge).
|
| 75 |
-
Named after Margherita Beloch, who showed in 1936 that this fold solves general cubic equations.
|
| 76 |
-
|
| 77 |
-
### Axiom 7: Point-to-Line Perpendicular to Line
|
| 78 |
-
- **Input**: One point p and two lines l1, l2
|
| 79 |
-
- **Output**: A fold that places p onto l1 and is perpendicular to l2
|
| 80 |
-
- **Geometric equivalent**: Constructing a perpendicular to a line that maps a point onto another line
|
| 81 |
-
- **Solutions**: 0 or 1
|
| 82 |
-
- **Parameters**: `(p_x, p_y, l1_point, l1_direction, l2_point, l2_direction)`
|
| 83 |
-
|
| 84 |
-
### Summary Table: Axiom Action Parameters
|
| 85 |
-
|
| 86 |
-
| Axiom | Inputs | Max Solutions | Solves | Key Use |
|
| 87 |
-
|-------|--------|---------------|--------|---------|
|
| 88 |
-
| O1 | 2 points | 1 | Linear | Line through two points |
|
| 89 |
-
| O2 | 2 points | 1 | Linear | Perpendicular bisector |
|
| 90 |
-
| O3 | 2 lines | 2 | Linear | Angle bisector |
|
| 91 |
-
| O4 | 1 point + 1 line | 1 | Linear | Perpendicular through point |
|
| 92 |
-
| O5 | 2 points + 1 line | 2 | Quadratic | Point onto line via point |
|
| 93 |
-
| O6 | 2 points + 2 lines | 3 | **Cubic** | Simultaneous alignment |
|
| 94 |
-
| O7 | 1 point + 2 lines | 1 | Quadratic | Perpendicular alignment |
|
| 95 |
-
|
| 96 |
-
### Mathematical Power
|
| 97 |
-
|
| 98 |
-
- **Compass + straightedge**: Solves up to degree-2 (quadratic) equations
|
| 99 |
-
- **Origami (Axioms 1-7)**: Solves up to degree-3 (cubic) equations
|
| 100 |
-
- **Multi-fold origami** (simultaneous folds): Can solve higher-degree equations
|
| 101 |
-
|
| 102 |
-
---
|
| 103 |
-
|
| 104 |
-
## 2. All Fold Types -- Compound Operations
|
| 105 |
-
|
| 106 |
-
While the Huzita-Justin axioms are the mathematical primitives, origami practice uses
|
| 107 |
-
**compound fold types** -- named sequences that combine one or more axiom applications
|
| 108 |
-
and layer manipulations. These are the operations an origami folder actually thinks in.
|
| 109 |
-
|
| 110 |
-
### 2.1 Valley Fold (Tani-ori)
|
| 111 |
-
|
| 112 |
-
**The most fundamental fold.**
|
| 113 |
-
|
| 114 |
-
- **Geometry**: Paper is folded toward the viewer along a straight crease line. Creates a
|
| 115 |
-
V-shaped cross-section (concave when viewed from above).
|
| 116 |
-
- **Dihedral angle**: gamma > 0 (positive fold angle between face normals)
|
| 117 |
-
- **Flat-folded state**: gamma = +pi (180 degrees)
|
| 118 |
-
- **Notation**: Dashed line (---) with filled arrowhead showing direction of motion
|
| 119 |
-
- **Parameters**:
|
| 120 |
-
- `fold_line`: defined by a point and direction vector (or two points)
|
| 121 |
-
- `fold_angle`: 0 to pi (typically pi for flat fold)
|
| 122 |
-
- `layers_affected`: which layers of paper are being folded
|
| 123 |
-
- **Axiom mapping**: Corresponds to any of Axioms 1-7 depending on how the fold line is determined
|
| 124 |
-
- **Decomposition**: Single atomic operation
|
| 125 |
-
|
| 126 |
-
### 2.2 Mountain Fold (Yama-ori)
|
| 127 |
-
|
| 128 |
-
**The complement of valley fold.**
|
| 129 |
-
|
| 130 |
-
- **Geometry**: Paper is folded away from the viewer along a straight crease line. Creates an
|
| 131 |
-
inverted-V cross-section (convex when viewed from above).
|
| 132 |
-
- **Dihedral angle**: gamma < 0 (negative fold angle)
|
| 133 |
-
- **Flat-folded state**: gamma = -pi (-180 degrees)
|
| 134 |
-
- **Notation**: Dot-dash line (-.-.-) with hollow single-sided arrowhead
|
| 135 |
-
- **Parameters**: Same as valley fold
|
| 136 |
-
- **Relationship to valley**: A mountain fold is geometrically identical to a valley fold viewed
|
| 137 |
-
from the other side. Flipping the paper converts all mountains to valleys and vice versa.
|
| 138 |
-
- **Decomposition**: Single atomic operation (equivalent to: turn paper over + valley fold + turn back)
|
| 139 |
-
|
| 140 |
-
### 2.3 Inside Reverse Fold
|
| 141 |
-
|
| 142 |
-
**Used extensively for heads, tails, beaks, and feet in animal models.**
|
| 143 |
-
|
| 144 |
-
- **Geometry**: A pointed flap (at least 2 layers) is opened and the tip is pushed inward,
|
| 145 |
-
reversing the direction of the central crease while creating two new creases on either side.
|
| 146 |
-
- **What happens**: The mountain fold along the spine of the flap is reversed (becomes valley)
|
| 147 |
-
between two new valley folds that diverge from a point on the original spine.
|
| 148 |
-
- **Parameters**:
|
| 149 |
-
- `flap_to_reverse`: which flap/point
|
| 150 |
-
- `fold_line_angle`: angle of the new crease relative to the flap spine
|
| 151 |
-
- `fold_depth`: how far down the spine the reversal point sits
|
| 152 |
-
- **Decomposition**: 2 valley folds + 1 mountain-to-valley conversion = 3 crease changes
|
| 153 |
-
- **Prerequisite state**: Requires an existing folded flap with a central crease
|
| 154 |
-
|
| 155 |
-
### 2.4 Outside Reverse Fold
|
| 156 |
-
|
| 157 |
-
**The mirror complement of inside reverse fold.**
|
| 158 |
-
|
| 159 |
-
- **Geometry**: A pointed flap is opened and the tip is wrapped around the outside, reversing
|
| 160 |
-
the central crease while creating two new creases.
|
| 161 |
-
- **What happens**: The valley fold along the spine becomes mountain, and two mountain folds
|
| 162 |
-
diverge from a point on the spine. The flap wraps over the outside.
|
| 163 |
-
- **Parameters**: Same as inside reverse fold
|
| 164 |
-
- **Decomposition**: 2 mountain folds + 1 valley-to-mountain conversion = 3 crease changes
|
| 165 |
-
- **Notation**: Mountain fold lines on near layer, valley on far layer, push arrow
|
| 166 |
-
|
| 167 |
-
### 2.5 Squash Fold
|
| 168 |
-
|
| 169 |
-
**Opens a flap and flattens it symmetrically.**
|
| 170 |
-
|
| 171 |
-
- **Geometry**: A flap with at least 2 layers has its closed edge opened. A radial fold from
|
| 172 |
-
the closed point bisects the flap. The flap is pressed flat, creating two adjacent flaps
|
| 173 |
-
from one.
|
| 174 |
-
- **What happens**: One existing crease becomes a fold, a new crease bisects the original flap,
|
| 175 |
-
and the flap is flattened into a diamond/square shape.
|
| 176 |
-
- **Parameters**:
|
| 177 |
-
- `flap_to_squash`: which flap
|
| 178 |
-
- `bisecting_line`: the line that will become the new center (typically the symmetry axis)
|
| 179 |
-
- `direction`: which side to flatten toward
|
| 180 |
-
- **Decomposition**: 1 new valley fold (bisector) + opening + flattening = compound operation
|
| 181 |
-
- **Common use**: Preliminary base -> bird base transition; creating diamond shapes from triangular flaps
|
| 182 |
-
|
| 183 |
-
### 2.6 Petal Fold
|
| 184 |
-
|
| 185 |
-
**The signature fold of the bird base. Creates a long, narrow flap from a wider one.**
|
| 186 |
-
|
| 187 |
-
- **Geometry**: Starting with two connected flaps (each with 2+ layers), two radial folds
|
| 188 |
-
are made from the open point so that the open edges lie along a reference crease. The top
|
| 189 |
-
layer is lifted and folded upward while the sides collapse inward.
|
| 190 |
-
- **What happens**: A point is elongated by folding two edges to a center line, then lifting
|
| 191 |
-
the top layer while the creases collapse. Essentially two symmetrically-placed rabbit ears
|
| 192 |
-
executed simultaneously.
|
| 193 |
-
- **Parameters**:
|
| 194 |
-
- `reference_crease`: the center line to fold edges toward
|
| 195 |
-
- `flap_edges`: the two edges that will be brought to the center
|
| 196 |
-
- `lift_direction`: up or down
|
| 197 |
-
- **Decomposition**: 2 valley folds (edges to center) + 1 valley fold (top layer lift) +
|
| 198 |
-
2 mountain folds (collapse) = 5 simultaneous crease changes
|
| 199 |
-
- **Common use**: Central operation in creating the bird base from preliminary base
|
| 200 |
-
|
| 201 |
-
### 2.7 Rabbit Ear Fold
|
| 202 |
-
|
| 203 |
-
**Creates a triangular flap that stands up from the surface.**
|
| 204 |
-
|
| 205 |
-
- **Geometry**: Starting with a triangular region, fold the angle bisectors from two corners
|
| 206 |
-
on the same side of a reference diagonal. The resulting triangular flap is folded flat to
|
| 207 |
-
one side.
|
| 208 |
-
- **What happens**: Three creases meet at a point -- two valley folds (bisectors) and one
|
| 209 |
-
mountain fold (the ridge of the raised triangle). The excess paper forms a small triangular
|
| 210 |
-
flap.
|
| 211 |
-
- **Parameters**:
|
| 212 |
-
- `reference_crease`: the diagonal or edge used as the base
|
| 213 |
-
- `bisector_1_angle`: angle of first bisector fold
|
| 214 |
-
- `bisector_2_angle`: angle of second bisector fold
|
| 215 |
-
- `flap_direction`: which side the resulting flap folds to
|
| 216 |
-
- **Decomposition**: 2 valley folds + 1 mountain fold = 3 simultaneous creases
|
| 217 |
-
- **Common use**: Fish base construction, creating narrow triangular points
|
| 218 |
-
|
| 219 |
-
### 2.8 Sink Fold (Three Variants)
|
| 220 |
-
|
| 221 |
-
**Pushes a point or corner into the interior of the model. The most difficult standard fold.**
|
| 222 |
-
|
| 223 |
-
#### 2.8a Open Sink
|
| 224 |
-
- **Geometry**: A corner point is pushed inward, and the paper around it is opened and
|
| 225 |
-
reflattened. The surrounding paper forms a waterbomb-base-like configuration around
|
| 226 |
-
the sunken area.
|
| 227 |
-
- **What happens**: Creases around the point are reversed (mountains become valleys and
|
| 228 |
-
vice versa). The paper can be opened flat during the process.
|
| 229 |
-
- **Parameters**:
|
| 230 |
-
- `point_to_sink`: which vertex/corner
|
| 231 |
-
- `sink_depth`: how far to push in (defined by a crease line around the point)
|
| 232 |
-
- `crease_pattern`: the polygon of creases that defines the sink boundary
|
| 233 |
-
- **Decomposition**: Multiple crease reversals; the paper is opened, re-creased, re-collapsed
|
| 234 |
-
- **Difficulty**: Intermediate
|
| 235 |
-
|
| 236 |
-
#### 2.8b Closed Sink
|
| 237 |
-
- **Geometry**: Same goal as open sink, but the paper layers cannot be separated during the
|
| 238 |
-
operation. The corner is pushed in while the paper remains closed.
|
| 239 |
-
- **What happens**: The point inverts without opening. Paper layers become deeply intertwined.
|
| 240 |
-
All axial creases around the point become mountain folds (key difference from open sink).
|
| 241 |
-
- **Parameters**: Same as open sink
|
| 242 |
-
- **Decomposition**: Simultaneous reversal of multiple creases without opening
|
| 243 |
-
- **Difficulty**: Advanced -- requires significant force; layers lock together
|
| 244 |
-
|
| 245 |
-
#### 2.8c Spread Sink (Spread Squash)
|
| 246 |
-
- **Geometry**: A closed flap or point is pushed in while the surrounding paper spreads
|
| 247 |
-
outward and flattens. The sinked analog of the squash fold.
|
| 248 |
-
- **What happens**: Creates a wide, flat area around the flap's base instead of a long point
|
| 249 |
-
- **Parameters**: Same as open sink + spread direction
|
| 250 |
-
- **Decomposition**: Sink + flatten/spread
|
| 251 |
-
- **Difficulty**: Advanced
|
| 252 |
-
|
| 253 |
-
### 2.9 Unsink Fold (Two Variants)
|
| 254 |
-
|
| 255 |
-
**The inverse of sink -- pops a sunken point back out.**
|
| 256 |
-
|
| 257 |
-
#### 2.9a Open Unsink
|
| 258 |
-
- Makes a concave pocket convex without fully unfolding
|
| 259 |
-
- The opposite of an open sink
|
| 260 |
-
|
| 261 |
-
#### 2.9b Closed Unsink
|
| 262 |
-
- Inverts a closed sink without opening the paper
|
| 263 |
-
- Extremely difficult -- requires pulling (not pushing) hidden paper
|
| 264 |
-
- Involves simultaneously folding a locking flap hidden inside
|
| 265 |
-
|
| 266 |
-
### 2.10 Crimp Fold
|
| 267 |
-
|
| 268 |
-
**A reverse fold applied to an edge rather than a point.**
|
| 269 |
-
|
| 270 |
-
- **Geometry**: Opens a section of paper, applies a valley fold, then a mountain fold with
|
| 271 |
-
some paper between them, creating a zigzag profile.
|
| 272 |
-
- **What happens**: Two parallel or near-parallel creases are created, with the paper between
|
| 273 |
-
them forming a step. The edge changes direction.
|
| 274 |
-
- **Parameters**:
|
| 275 |
-
- `crimp_location`: where along the edge
|
| 276 |
-
- `fold_line_1`: first crease (valley)
|
| 277 |
-
- `fold_line_2`: second crease (mountain)
|
| 278 |
-
- `crimp_width`: distance between the two creases
|
| 279 |
-
- `crimp_angle`: angle of direction change
|
| 280 |
-
- **Decomposition**: 1 valley fold + 1 mountain fold applied simultaneously to a multi-layer section
|
| 281 |
-
- **Relationship**: Similar to pleat fold but applied to a folded edge (multiple layers);
|
| 282 |
-
like an inside reverse fold but without full reversal
|
| 283 |
-
- **Common use**: Creating feet, zigzag shapes, direction changes in legs
|
| 284 |
-
|
| 285 |
-
### 2.11 Pleat Fold (Accordion Fold)
|
| 286 |
-
|
| 287 |
-
**The simplest compound fold -- alternating valleys and mountains.**
|
| 288 |
-
|
| 289 |
-
- **Geometry**: A series of parallel or near-parallel alternating valley and mountain folds.
|
| 290 |
-
Creates a zigzag/accordion profile.
|
| 291 |
-
- **What happens**: Paper forms parallel ridges and valleys, like a fan or accordion.
|
| 292 |
-
- **Parameters**:
|
| 293 |
-
- `fold_lines[]`: array of parallel crease lines
|
| 294 |
-
- `fold_types[]`: alternating valley/mountain assignments
|
| 295 |
-
- `pleat_width`: distance between consecutive creases (can be uniform or varying)
|
| 296 |
-
- `num_pleats`: number of folds
|
| 297 |
-
- **Decomposition**: n alternating valley + mountain folds
|
| 298 |
-
- **Common use**: Adding detail (pleats in clothing), creating segmented forms, fan shapes
|
| 299 |
-
|
| 300 |
-
### 2.12 Swivel Fold
|
| 301 |
-
|
| 302 |
-
**A loosely-defined fold where one flap "swivels" around a pivot point.**
|
| 303 |
-
|
| 304 |
-
- **Geometry**: A flap of paper rotates around a specific vertex or point while a connected
|
| 305 |
-
flap or edge is dragged around that pivot. One fold inevitably causes another.
|
| 306 |
-
- **What happens**: Simultaneous folding of multiple connected regions around a pivot point.
|
| 307 |
-
One fold "trails" another.
|
| 308 |
-
- **Parameters**:
|
| 309 |
-
- `pivot_point`: the vertex around which paper swivels
|
| 310 |
-
- `primary_fold_line`: the main fold being executed
|
| 311 |
-
- `trailing_fold_line`: the induced fold
|
| 312 |
-
- `swivel_angle`: angle of rotation
|
| 313 |
-
- **Decomposition**: 2+ simultaneous folds (one driving, one or more trailing)
|
| 314 |
-
- **Note**: Loosely defined; many different configurations qualify as "swivel folds"
|
| 315 |
-
- **Common use**: Shaping flaps, creating offset angles, bird tails
|
| 316 |
-
|
| 317 |
-
### Summary: Fold Type Taxonomy
|
| 318 |
-
|
| 319 |
-
```
|
| 320 |
-
ATOMIC FOLDS (single crease, corresponds to 1 axiom application):
|
| 321 |
-
|-- Valley Fold (gamma > 0)
|
| 322 |
-
|-- Mountain Fold (gamma < 0)
|
| 323 |
-
|
| 324 |
-
COMPOUND FOLDS (multiple simultaneous creases):
|
| 325 |
-
|-- Reverse Folds
|
| 326 |
-
| |-- Inside Reverse Fold (3 crease changes)
|
| 327 |
-
| |-- Outside Reverse Fold (3 crease changes)
|
| 328 |
-
|
|
| 329 |
-
|-- Squash Fold (2-3 crease changes)
|
| 330 |
-
|
|
| 331 |
-
|-- Petal Fold (5 simultaneous crease changes)
|
| 332 |
-
|
|
| 333 |
-
|-- Rabbit Ear Fold (3 simultaneous creases)
|
| 334 |
-
|
|
| 335 |
-
|-- Sink Folds
|
| 336 |
-
| |-- Open Sink (multiple reversals, paper opens)
|
| 337 |
-
| |-- Closed Sink (multiple reversals, paper stays closed)
|
| 338 |
-
| |-- Spread Sink (sink + flatten)
|
| 339 |
-
|
|
| 340 |
-
|-- Unsink Folds
|
| 341 |
-
| |-- Open Unsink (reverse of open sink)
|
| 342 |
-
| |-- Closed Unsink (reverse of closed sink)
|
| 343 |
-
|
|
| 344 |
-
|-- Crimp Fold (2 simultaneous folds on multi-layer edge)
|
| 345 |
-
|
|
| 346 |
-
|-- Pleat Fold (n alternating valley/mountain folds)
|
| 347 |
-
|
|
| 348 |
-
|-- Swivel Fold (2+ coupled folds around pivot)
|
| 349 |
-
|
| 350 |
-
NON-FOLD OPERATIONS (manipulations):
|
| 351 |
-
|-- Turn Over (flip paper)
|
| 352 |
-
|-- Rotate (in-plane rotation)
|
| 353 |
-
|-- Inflate / Open (spread layers apart, puff out)
|
| 354 |
-
|-- Unfold (reverse a previous fold)
|
| 355 |
-
|-- Cut (kirigami -- not standard origami)
|
| 356 |
-
```
|
| 357 |
-
|
| 358 |
-
### Fold Parameter Space
|
| 359 |
-
|
| 360 |
-
For an RL environment, each fold can be parameterized as:
|
| 361 |
-
|
| 362 |
-
```
|
| 363 |
-
Action = {
|
| 364 |
-
fold_type: enum[valley, mountain, reverse_in, reverse_out, squash, petal,
|
| 365 |
-
rabbit_ear, sink_open, sink_closed, spread_sink,
|
| 366 |
-
unsink_open, unsink_closed, crimp, pleat, swivel,
|
| 367 |
-
turn_over, rotate, inflate, unfold]
|
| 368 |
-
|
| 369 |
-
-- For atomic folds (valley/mountain):
|
| 370 |
-
fold_line: (point: vec2, direction: vec2) -- or (point1, point2)
|
| 371 |
-
fold_angle: float [0, pi] -- how far to fold
|
| 372 |
-
layers: bitset -- which layers to fold
|
| 373 |
-
|
| 374 |
-
-- For compound folds, additional params:
|
| 375 |
-
target_flap: flap_id -- which flap to operate on
|
| 376 |
-
depth: float -- for reverse/sink: how deep
|
| 377 |
-
angle: float -- for reverse/crimp: fold angle
|
| 378 |
-
direction: enum[left, right, up, down] -- fold direction preference
|
| 379 |
-
width: float -- for crimp/pleat: spacing
|
| 380 |
-
|
| 381 |
-
-- For manipulation operations:
|
| 382 |
-
rotation_angle: float -- for rotate
|
| 383 |
-
axis: enum[horizontal, vertical] -- for turn_over
|
| 384 |
-
}
|
| 385 |
-
```
|
| 386 |
-
|
| 387 |
-
---
|
| 388 |
-
|
| 389 |
-
## 3. Origami Bases -- Starting Configurations
|
| 390 |
-
|
| 391 |
-
Bases are standard intermediate forms that serve as starting points for families of models.
|
| 392 |
-
**Yes, bases are shortcuts for common fold sequences.** They represent well-known crease
|
| 393 |
-
patterns that produce useful distributions of flaps and points.
|
| 394 |
-
|
| 395 |
-
### 3.1 Preliminary Base (Square Base)
|
| 396 |
-
|
| 397 |
-
- **Shape**: Multi-layered diamond/square standing on a corner
|
| 398 |
-
- **Flaps**: 4 flaps (2 front, 2 back)
|
| 399 |
-
- **Points**: 1 closed point at top, 4 open edges at bottom
|
| 400 |
-
- **Construction from flat square**:
|
| 401 |
-
1. Valley fold in half horizontally (1 fold)
|
| 402 |
-
2. Valley fold in half vertically (1 fold)
|
| 403 |
-
3. Unfold both (0 folds -- return to flat with creases)
|
| 404 |
-
4. Valley fold diagonally both ways (2 folds, unfold)
|
| 405 |
-
5. Collapse: push sides in using existing creases (simultaneous collapse)
|
| 406 |
-
- **Total atomic folds**: ~4 creases + 1 collapse = ~5 operations
|
| 407 |
-
- **Crease pattern**: 2 perpendicular valley diagonals + 2 perpendicular mountain edge-bisectors
|
| 408 |
-
- **Key relationship**: Inverse of Waterbomb base (same creases, swapped mountain/valley)
|
| 409 |
-
- **Gateway to**: Bird base, Frog base, Flower base
|
| 410 |
-
|
| 411 |
-
### 3.2 Waterbomb Base
|
| 412 |
-
|
| 413 |
-
- **Shape**: Flat triangle with multiple layers
|
| 414 |
-
- **Flaps**: 4 flaps (2 front, 2 back)
|
| 415 |
-
- **Points**: 4 points at base, closed edges at sides
|
| 416 |
-
- **Construction from flat square**:
|
| 417 |
-
1. Valley fold both diagonals (2 folds, unfold)
|
| 418 |
-
2. Mountain fold horizontally and vertically (2 folds, unfold)
|
| 419 |
-
3. Collapse into triangle form
|
| 420 |
-
- **Total atomic folds**: ~4 creases + 1 collapse = ~5 operations
|
| 421 |
-
- **Crease pattern**: 2 perpendicular mountain diagonals + 2 perpendicular valley edge-bisectors
|
| 422 |
-
- **Key relationship**: Inverse of Preliminary base (same creases, swapped mountain/valley)
|
| 423 |
-
- **Gateway to**: Waterbomb (balloon), waterbomb tessellations, some Frog base variants
|
| 424 |
-
|
| 425 |
-
### 3.3 Kite Base
|
| 426 |
-
|
| 427 |
-
- **Shape**: Kite-shaped flat form
|
| 428 |
-
- **Flaps**: 1 main point, 2 small triangular flaps
|
| 429 |
-
- **Construction from flat square**:
|
| 430 |
-
1. Valley fold diagonal (1 fold, unfold -- reference crease)
|
| 431 |
-
2. Valley fold two adjacent edges to lie on the diagonal (2 folds)
|
| 432 |
-
- **Total atomic folds**: 3 (simplest base)
|
| 433 |
-
- **Gateway to**: Simple animals (dog face, cat face), Kite -> Fish base progression
|
| 434 |
-
|
| 435 |
-
### 3.4 Fish Base
|
| 436 |
-
|
| 437 |
-
- **Shape**: Diamond shape with 4 points (two long, two short)
|
| 438 |
-
- **Flaps**: 4 points usable for fins/legs/petals
|
| 439 |
-
- **Construction from flat square**:
|
| 440 |
-
1. Start with kite base (3 folds)
|
| 441 |
-
2. Rabbit ear fold on one end (3 simultaneous creases)
|
| 442 |
-
3. Rabbit ear fold on other end (3 simultaneous creases)
|
| 443 |
-
4. Fold resulting flaps down (2 folds)
|
| 444 |
-
- **Total atomic folds**: ~8-11 operations
|
| 445 |
-
- **Crease pattern**: Two rabbit ears against diagonal reference creases on opposite corners
|
| 446 |
-
- **Gateway to**: Fish models, some flower models
|
| 447 |
-
|
| 448 |
-
### 3.5 Bird Base (Crane Base)
|
| 449 |
-
|
| 450 |
-
- **Shape**: Long diamond with 4 narrow flaps
|
| 451 |
-
- **Flaps**: 4 long, narrow flaps (2 front, 2 back)
|
| 452 |
-
- **Points**: All 4 original corners become elongated points
|
| 453 |
-
- **Construction from flat square**:
|
| 454 |
-
1. Fold preliminary base (~5 operations)
|
| 455 |
-
2. Kite-fold front flaps: fold left and right edges to center line (2 valley folds)
|
| 456 |
-
3. Fold top triangle down over the kite folds (1 valley fold, then unfold)
|
| 457 |
-
4. Unfold the kite folds (restore)
|
| 458 |
-
5. Petal fold: lift bottom point up using existing creases (1 petal fold = ~5 crease changes)
|
| 459 |
-
6. Turn over
|
| 460 |
-
7. Repeat steps 2-5 on back side (mirror)
|
| 461 |
-
- **Total atomic folds**: ~18-22 operations from flat square
|
| 462 |
-
- **Key significance**: The most versatile and widely-used origami base
|
| 463 |
-
- **Gateway to**: Crane, many birds, dragon, horse, and hundreds of other models
|
| 464 |
-
|
| 465 |
-
### 3.6 Frog Base
|
| 466 |
-
|
| 467 |
-
- **Shape**: 4 long narrow flaps radiating from center (more symmetrical than bird base)
|
| 468 |
-
- **Flaps**: 4 long flaps + 4 shorter flaps
|
| 469 |
-
- **Construction from flat square**:
|
| 470 |
-
1. Fold preliminary base (~5 operations)
|
| 471 |
-
2. Squash fold each of the 4 flaps (4 squash folds)
|
| 472 |
-
3. Petal fold each of the 4 resulting diamonds (4 petal folds)
|
| 473 |
-
- **Total atomic folds**: ~25-30 operations from flat square
|
| 474 |
-
- **Gateway to**: Frog, lily, iris, and other 4-legged/4-petal models
|
| 475 |
-
|
| 476 |
-
### 3.7 Windmill Base
|
| 477 |
-
|
| 478 |
-
- **Shape**: 4 triangular flaps arranged like a pinwheel
|
| 479 |
-
- **Flaps**: 4 flaps that can rotate in a windmill pattern
|
| 480 |
-
- **Construction from flat square**:
|
| 481 |
-
1. Fold and unfold both diagonals and both midlines (4 creases)
|
| 482 |
-
2. Fold all 4 edges to center (4 valley folds) -- this is the "blintz fold"
|
| 483 |
-
3. Unfold
|
| 484 |
-
4. Fold 2 opposite edges to center (2 valley folds)
|
| 485 |
-
5. Pull out trapped corners and flatten (2 squash-like operations)
|
| 486 |
-
- **Total atomic folds**: ~12-15 operations
|
| 487 |
-
- **Gateway to**: Windmill, some modular units, windmill-derived models
|
| 488 |
-
|
| 489 |
-
### Base Relationship Map
|
| 490 |
-
|
| 491 |
-
```
|
| 492 |
-
Flat Square
|
| 493 |
-
|
|
| 494 |
-
+---> Kite Base ---------> Fish Base
|
| 495 |
-
| (3 folds) (8-11 folds)
|
| 496 |
-
|
|
| 497 |
-
+---> Preliminary Base ---> Bird Base ------> [Crane, Birds, etc.]
|
| 498 |
-
| (5 folds) (18-22 folds)
|
| 499 |
-
| |
|
| 500 |
-
| +-> Frog Base -----> [Frog, Lily, etc.]
|
| 501 |
-
| (25-30 folds)
|
| 502 |
-
|
|
| 503 |
-
+---> Waterbomb Base -----> [Waterbomb/Balloon, etc.]
|
| 504 |
-
| (5 folds)
|
| 505 |
-
|
|
| 506 |
-
+---> Blintz Base --------> Windmill Base -> [Windmill, etc.]
|
| 507 |
-
| (8 folds) (12-15 folds)
|
| 508 |
-
|
|
| 509 |
-
+---> Book Fold / Cupboard Fold / etc.
|
| 510 |
-
```
|
| 511 |
-
|
| 512 |
-
---
|
| 513 |
-
|
| 514 |
-
## 4. Crane (Tsuru) Complete Fold Sequence
|
| 515 |
-
|
| 516 |
-
The traditional origami crane (orizuru) is THE canonical origami model, and the best
|
| 517 |
-
benchmark for an RL environment. Here is the complete fold-by-fold sequence from flat
|
| 518 |
-
square to finished crane.
|
| 519 |
-
|
| 520 |
-
### Phase 1: Create Crease Pattern (Pre-creasing)
|
| 521 |
-
|
| 522 |
-
| Step | Operation | Fold Type | Fold Line | Result |
|
| 523 |
-
|------|-----------|-----------|-----------|--------|
|
| 524 |
-
| 1 | Fold square in half diagonally (corner to corner) | Valley fold | Main diagonal (bottom-left to top-right) | Triangle |
|
| 525 |
-
| 2 | Unfold | Unfold | -- | Square with diagonal crease |
|
| 526 |
-
| 3 | Fold in half on other diagonal | Valley fold | Other diagonal (bottom-right to top-left) | Triangle |
|
| 527 |
-
| 4 | Unfold | Unfold | -- | Square with X crease |
|
| 528 |
-
| 5 | Fold in half horizontally | Valley fold | Horizontal midline | Rectangle |
|
| 529 |
-
| 6 | Unfold | Unfold | -- | Square with X + horizontal crease |
|
| 530 |
-
| 7 | Fold in half vertically | Valley fold | Vertical midline | Rectangle |
|
| 531 |
-
| 8 | Unfold | Unfold | -- | Square with full crease pattern (X + cross) |
|
| 532 |
-
|
| 533 |
-
### Phase 2: Collapse into Preliminary Base
|
| 534 |
-
|
| 535 |
-
| Step | Operation | Fold Type | Details | Result |
|
| 536 |
-
|------|-----------|-----------|---------|--------|
|
| 537 |
-
| 9 | Collapse: push left and right edges inward while folding top down | Simultaneous collapse | Diagonals become valley folds, midlines become mountain folds | Preliminary base (4-layer diamond) |
|
| 538 |
-
|
| 539 |
-
### Phase 3: Kite Folds (Front)
|
| 540 |
-
|
| 541 |
-
| Step | Operation | Fold Type | Fold Line | Result |
|
| 542 |
-
|------|-----------|-----------|-----------|--------|
|
| 543 |
-
| 10 | Fold left edge of top layer to center line | Valley fold | Left edge to center crease | Left kite flap |
|
| 544 |
-
| 11 | Fold right edge of top layer to center line | Valley fold | Right edge to center crease | Kite shape |
|
| 545 |
-
| 12 | Fold top triangle down over kite flaps | Valley fold | Horizontal line at top of kite flaps | Triangle folded over |
|
| 546 |
-
| 13 | Unfold step 12 | Unfold | -- | Kite shape with horizontal crease |
|
| 547 |
-
| 14 | Unfold steps 10-11 | Unfold | -- | Diamond with crease guides |
|
| 548 |
-
|
| 549 |
-
### Phase 4: Front Petal Fold
|
| 550 |
-
|
| 551 |
-
| Step | Operation | Fold Type | Details | Result |
|
| 552 |
-
|------|-----------|-----------|---------|--------|
|
| 553 |
-
| 15 | Lift bottom point of top layer upward using existing creases; sides collapse inward | Petal fold | Bottom point lifts to top; left and right edges fold to center simultaneously | Front petal fold complete -- one long narrow flap pointing up |
|
| 554 |
-
|
| 555 |
-
### Phase 5: Repeat on Back
|
| 556 |
-
|
| 557 |
-
| Step | Operation | Fold Type | Details | Result |
|
| 558 |
-
|------|-----------|-----------|---------|--------|
|
| 559 |
-
| 16 | Turn model over | Turn over | Flip along vertical axis | Back is now front |
|
| 560 |
-
| 17 | Fold left edge to center line | Valley fold | Left edge to center | Left kite flap |
|
| 561 |
-
| 18 | Fold right edge to center line | Valley fold | Right edge to center | Kite shape |
|
| 562 |
-
| 19 | Fold top triangle down | Valley fold | Horizontal line at top | Triangle folded over |
|
| 563 |
-
| 20 | Unfold step 19 | Unfold | -- | Kite with crease |
|
| 564 |
-
| 21 | Unfold steps 17-18 | Unfold | -- | Diamond with creases |
|
| 565 |
-
| 22 | Petal fold: lift bottom point up, collapse sides in | Petal fold | Same as step 15 on back | Bird base complete |
|
| 566 |
-
|
| 567 |
-
### Phase 6: Narrow the Legs
|
| 568 |
-
|
| 569 |
-
| Step | Operation | Fold Type | Details | Result |
|
| 570 |
-
|------|-----------|-----------|---------|--------|
|
| 571 |
-
| 23 | Fold left flap (front layer) edge to center | Valley fold | Left edge to centerline | Narrower left flap |
|
| 572 |
-
| 24 | Fold right flap (front layer) edge to center | Valley fold | Right edge to centerline | Narrower right flap |
|
| 573 |
-
| 25 | Turn over | Turn over | -- | Back side |
|
| 574 |
-
| 26 | Fold left flap edge to center | Valley fold | Left edge to centerline | Narrower left flap (back) |
|
| 575 |
-
| 27 | Fold right flap edge to center | Valley fold | Right edge to centerline | Narrower right flap (back) |
|
| 576 |
-
|
| 577 |
-
### Phase 7: Form Neck and Tail
|
| 578 |
-
|
| 579 |
-
| Step | Operation | Fold Type | Details | Result |
|
| 580 |
-
|------|-----------|-----------|---------|--------|
|
| 581 |
-
| 28 | Fold left bottom flap (front+back layers) upward along angle to form neck | Inside reverse fold | Fold line at ~60-70 degrees from vertical | Neck points upward at angle |
|
| 582 |
-
| 29 | Fold right bottom flap upward to form tail | Inside reverse fold | Fold line at ~60-70 degrees from vertical | Tail points upward |
|
| 583 |
-
|
| 584 |
-
### Phase 8: Form Head and Finish
|
| 585 |
-
|
| 586 |
-
| Step | Operation | Fold Type | Details | Result |
|
| 587 |
-
|------|-----------|-----------|---------|--------|
|
| 588 |
-
| 30 | Fold tip of neck downward to form head/beak | Inside reverse fold | Small fold near tip of neck, ~30 degrees | Head with beak |
|
| 589 |
-
| 31 | Gently pull wings apart from body and press bottom to create 3D body | Open/Inflate | Separate wing layers, crease body | Finished crane |
|
| 590 |
-
|
| 591 |
-
### Crane Fold Statistics
|
| 592 |
-
|
| 593 |
-
| Metric | Count |
|
| 594 |
-
|--------|-------|
|
| 595 |
-
| **Total numbered steps** | 31 |
|
| 596 |
-
| **Valley folds** | 14 |
|
| 597 |
-
| **Unfolds** | 6 |
|
| 598 |
-
| **Petal folds** | 2 (each = ~5 atomic crease changes) |
|
| 599 |
-
| **Inside reverse folds** | 3 (each = ~3 atomic crease changes) |
|
| 600 |
-
| **Turn over** | 2 |
|
| 601 |
-
| **Collapse** | 1 (= ~4 simultaneous crease changes) |
|
| 602 |
-
| **Open/inflate** | 1 |
|
| 603 |
-
| **Total atomic crease changes** | ~40-45 |
|
| 604 |
-
| **Unique fold types used** | 5 (valley, petal, inside reverse, collapse, inflate) |
|
| 605 |
-
|
| 606 |
-
---
|
| 607 |
-
|
| 608 |
-
## 5. Compression/Packing Origami Patterns
|
| 609 |
-
|
| 610 |
-
These are tessellation and pattern-based folds used in engineering, not traditional origami
|
| 611 |
-
art. Critical for understanding the space of possible flat-to-compact transformations.
|
| 612 |
-
|
| 613 |
-
### 5.1 Miura-ori Fold
|
| 614 |
-
|
| 615 |
-
- **Inventor**: Koryo Miura (1970)
|
| 616 |
-
- **Geometry**: Tessellation of parallelograms with alternating mountain/valley creases.
|
| 617 |
-
In one direction, creases are straight lines with mirror-reflected parallelograms.
|
| 618 |
-
In the other direction, creases zigzag, with parallelograms translated across creases.
|
| 619 |
-
- **Parameters**:
|
| 620 |
-
- Parallelogram angle (alpha): the acute angle of the parallelogram, typically 55-85 degrees
|
| 621 |
-
- Panel width and height
|
| 622 |
-
- Number of panels in each direction
|
| 623 |
-
- **Rigid-foldable**: YES -- can be folded with completely rigid (non-bending) panels
|
| 624 |
-
- **Degrees of freedom**: 1 (single DOF per unit cell). The entire sheet deploys/compacts
|
| 625 |
-
with a single motion.
|
| 626 |
-
- **Compression**: Folds flat in BOTH directions simultaneously. Unfolds by pulling opposite
|
| 627 |
-
corners apart in a single motion.
|
| 628 |
-
- **Poisson's ratio**: Always NEGATIVE (auxetic material). When you pull it in one direction,
|
| 629 |
-
it expands in the perpendicular direction too.
|
| 630 |
-
- **Compression ratio**: Depends on number of panels; theoretically approaches thickness-only
|
| 631 |
-
when fully compressed. A sheet can compress to approximately (n_panels * t) where t is
|
| 632 |
-
material thickness.
|
| 633 |
-
- **Applications**: Satellite solar panel arrays (Space Flyer Unit, 1995), metamaterials,
|
| 634 |
-
deployable shelters, foldable maps
|
| 635 |
-
- **Crease pattern**: Regular grid of mountain and valley folds at specific angles
|
| 636 |
-
|
| 637 |
-
### 5.2 Waterbomb Tessellation
|
| 638 |
-
|
| 639 |
-
- **Geometry**: Repeating waterbomb base units tessellated across a surface. Each unit has
|
| 640 |
-
degree-6 vertices with alternating mountain/valley folds.
|
| 641 |
-
- **Parameters**:
|
| 642 |
-
- Base unit size
|
| 643 |
-
- Grid dimensions
|
| 644 |
-
- Mountain/valley assignment per crease
|
| 645 |
-
- **Rigid-foldable**: Partially (depends on configuration)
|
| 646 |
-
- **Compression ratio**: ~3:1 (switching ratio)
|
| 647 |
-
- **Properties**: Can form curved surfaces; used in origami-inspired robots; basis for
|
| 648 |
-
many adaptive structures
|
| 649 |
-
- **Applications**: Soft robotics, adaptive surfaces, energy absorption
|
| 650 |
-
|
| 651 |
-
### 5.3 Ron Resch Pattern
|
| 652 |
-
|
| 653 |
-
- **Inventor**: Ron Resch (patent 1968)
|
| 654 |
-
- **Geometry**: Folded equilateral triangles arranged in periodic radial formation.
|
| 655 |
-
Six triangles around a central point compress together to form a flat-surfaced hexagon.
|
| 656 |
-
- **Parameters**:
|
| 657 |
-
- Triangle size
|
| 658 |
-
- Grid pattern (triangular or square variant)
|
| 659 |
-
- Fold depth
|
| 660 |
-
- **Rigid-foldable**: No (requires panel bending)
|
| 661 |
-
- **Compression ratio**: Up to 50:1 theoretical, ~6:1 practical (limited by creep)
|
| 662 |
-
- **Specific elastic compression modulus**: 15-365 MPa/kg for standard designs;
|
| 663 |
-
novel variants reach 594-926 MPa/kg
|
| 664 |
-
- **Properties**: Excellent energy absorption; creates two flat surfaces (top and bottom)
|
| 665 |
-
with triangular columns between them
|
| 666 |
-
- **Applications**: Impact damping, packaging, architectural surfaces, sandwich panel cores
|
| 667 |
-
|
| 668 |
-
### 5.4 Flasher Pattern
|
| 669 |
-
|
| 670 |
-
- **Geometry**: Central polygon (typically hexagon or octagon) whose edges connect to
|
| 671 |
-
extending panels, each with identical crease geometry. Panels spiral around the
|
| 672 |
-
central polygon when folded.
|
| 673 |
-
- **Parameters**:
|
| 674 |
-
- Central polygon type and size
|
| 675 |
-
- Number of extending panel rings
|
| 676 |
-
- Spiral angle
|
| 677 |
-
- Panel thickness (critical for rigid implementations)
|
| 678 |
-
- **Compression ratio**: Stowed-to-deployed diameter ratio of ~9.2:1 for typical designs;
|
| 679 |
-
area ratio much higher (square of diameter ratio, ~85:1)
|
| 680 |
-
- **Rigid-foldable**: With modifications (membrane hinges or diagonal folding to accommodate
|
| 681 |
-
panel thickness)
|
| 682 |
-
- **Degrees of freedom**: 1 (deploys/compacts with single rotational motion)
|
| 683 |
-
- **Applications**: Space solar arrays (250+ kW), solar sails, deployable reflectors,
|
| 684 |
-
NASA deployable structures
|
| 685 |
-
- **Key challenge**: Accommodating real material thickness; zero-thickness models don't
|
| 686 |
-
directly translate
|
| 687 |
-
|
| 688 |
-
### 5.5 Kresling Pattern
|
| 689 |
-
|
| 690 |
-
- **Inventor**: Biruta Kresling
|
| 691 |
-
- **Geometry**: Tessellated triangles forming a thin cylindrical structure. Diagonal fold
|
| 692 |
-
lines create helical creases around the cylinder.
|
| 693 |
-
- **Parameters**:
|
| 694 |
-
- Number of polygon sides (n, typically 6-8)
|
| 695 |
-
- Cylinder radius
|
| 696 |
-
- Triangle aspect ratio
|
| 697 |
-
- Folding angles (beta and gamma relative to horizontal)
|
| 698 |
-
- **Rigid-foldable**: NO (requires panel bending/deformation for compression)
|
| 699 |
-
- **Degrees of freedom**: Coupled -- compression induces twist (axial-torsional coupling)
|
| 700 |
-
- **Compression ratio**: ~1.5:1 switching ratio
|
| 701 |
-
- **Bistability**: Key property -- can snap between extended and compressed states.
|
| 702 |
-
Geometrical parameters can be tuned for mono-, bi-, or multistability.
|
| 703 |
-
- **Properties**: Compression-twist coupling, negative stiffness regions, energy storage
|
| 704 |
-
- **Applications**: Soft robots, mechanical metamaterials, deployable tubes, vibration isolation,
|
| 705 |
-
energy harvesting
|
| 706 |
-
|
| 707 |
-
### 5.6 Yoshimura Pattern
|
| 708 |
-
|
| 709 |
-
- **Geometry**: Diamond/rhombus pattern around a cylinder, creating a Yoshimura buckle
|
| 710 |
-
- **Rigid-foldable**: NO
|
| 711 |
-
- **Properties**: Natural buckling pattern of thin cylindrical shells under axial compression
|
| 712 |
-
- **Applications**: Crushable energy absorbers, deployable structures
|
| 713 |
-
|
| 714 |
-
### Pattern Comparison Table
|
| 715 |
-
|
| 716 |
-
| Pattern | Rigid-Foldable | DOF | Compression Ratio | Bistable | Key Property |
|
| 717 |
-
|---------|---------------|-----|-------------------|----------|--------------|
|
| 718 |
-
| Miura-ori | Yes | 1 | High (thickness-limited) | No | Auxetic (negative Poisson's ratio) |
|
| 719 |
-
| Waterbomb | Partial | Multi | ~3:1 | No | Curved surfaces possible |
|
| 720 |
-
| Ron Resch | No | Multi | ~6-50:1 | No | Excellent energy absorption |
|
| 721 |
-
| Flasher | Modified yes | 1 | ~9.2:1 (diameter) | No | Spiral deployment |
|
| 722 |
-
| Kresling | No | Coupled | ~1.5:1 | YES | Compression-twist coupling |
|
| 723 |
-
| Yoshimura | No | Multi | Moderate | No | Natural buckling mode |
|
| 724 |
-
|
| 725 |
-
---
|
| 726 |
-
|
| 727 |
-
## 6. Yoshizawa-Randlett Notation System
|
| 728 |
-
|
| 729 |
-
The standard international notation system for origami diagrams, created by Akira Yoshizawa
|
| 730 |
-
(1954) and formalized by Samuel Randlett and Robert Harbin (1961).
|
| 731 |
-
|
| 732 |
-
### 6.1 Line Types
|
| 733 |
-
|
| 734 |
-
| Line Style | Meaning | Description |
|
| 735 |
-
|------------|---------|-------------|
|
| 736 |
-
| Dashed line `------` | Valley fold | Paper folds toward you |
|
| 737 |
-
| Dot-dash line `-.-.-.` | Mountain fold | Paper folds away from you |
|
| 738 |
-
| Thin solid line | Existing crease | A crease already made in a previous step |
|
| 739 |
-
| Thick solid line | Paper edge | The boundary of the paper |
|
| 740 |
-
| Dotted line `......` | X-ray line | Hidden edge or crease visible through layers |
|
| 741 |
-
|
| 742 |
-
### 6.2 Arrow Types
|
| 743 |
-
|
| 744 |
-
| Arrow | Meaning | Visual |
|
| 745 |
-
|-------|---------|--------|
|
| 746 |
-
| Filled/split arrowhead, curved stem | Valley fold (fold toward you) | Shows rotation path of paper |
|
| 747 |
-
| Hollow single-sided arrowhead | Mountain fold (fold away) | Hooks behind moving flap |
|
| 748 |
-
| Double-sided hollow arrowheads | Fold and unfold | Make crease, then return |
|
| 749 |
-
| Double-sided hollow (on existing fold) | Unfold only | Reverse an existing fold |
|
| 750 |
-
| Arrow with loop in stem | Turn paper over | Flip horizontally or vertically |
|
| 751 |
-
| Hollow cleft-tailed arrow | Push / Apply pressure | Used for sinks, inflations, reverses |
|
| 752 |
-
| Arrow curving around layers | Hooking arrow | Shows which specific layers move |
|
| 753 |
-
| Arrow touching down multiple times | Fold over and over | Repeated folding |
|
| 754 |
-
|
| 755 |
-
### 6.3 Special Symbols
|
| 756 |
-
|
| 757 |
-
| Symbol | Meaning |
|
| 758 |
-
|--------|---------|
|
| 759 |
-
| Circle with fraction + curved arrows | Rotate in plane (e.g., 1/4 turn) |
|
| 760 |
-
| Box with step range + count | Repeat steps (e.g., "steps 5-8, x4") |
|
| 761 |
-
| Open circle on paper | Hold here / grip point |
|
| 762 |
-
| Perpendicular marks on edges | Equal distances |
|
| 763 |
-
| Arc marks on angles | Equal angles |
|
| 764 |
-
| Heavy circle around area | Cut-away view (shows hidden layers) |
|
| 765 |
-
| Zigzag line (edge view) | Crimp/pleat layer diagram |
|
| 766 |
-
| Stylized eye symbol | Next view location/angle |
|
| 767 |
-
| Scissors symbol | Cut (kirigami) |
|
| 768 |
-
| Puff/cloud symbol | Inflate / blow air |
|
| 769 |
-
|
| 770 |
-
### 6.4 Compound Fold Notation
|
| 771 |
-
|
| 772 |
-
| Fold Type | Notation Components |
|
| 773 |
-
|-----------|-------------------|
|
| 774 |
-
| Inside reverse | Mountain line (near layer) + valley line (far layer) + push arrow + valley motion arrow |
|
| 775 |
-
| Outside reverse | Paired mountain/valley lines + hooked arrows showing opposite directions |
|
| 776 |
-
| Squash | Valley fold line + push arrow + opening indicator |
|
| 777 |
-
| Petal | 2 mountain lines + 1 valley line + lift arrow |
|
| 778 |
-
| Rabbit ear | 3 valley lines (bisectors) + 1 mountain line + flap direction arrow |
|
| 779 |
-
| Sink | Mountain fold line + push arrow (hollow for open sink; dots at corners for closed) |
|
| 780 |
-
| Crimp | Edge-view zigzag diagram + paired fold lines |
|
| 781 |
-
|
| 782 |
-
---
|
| 783 |
-
|
| 784 |
-
## 7. Fold Sequence Complexity
|
| 785 |
-
|
| 786 |
-
### 7.1 Complexity Classification (OrigamiUSA Standard)
|
| 787 |
-
|
| 788 |
-
| Level | Steps | Time | Fold Types Used |
|
| 789 |
-
|-------|-------|------|-----------------|
|
| 790 |
-
| Simple | 1-16 | 5-15 min | Valley and mountain folds only |
|
| 791 |
-
| Low Intermediate | 10-20 | 10-20 min | + reverse folds |
|
| 792 |
-
| Intermediate | 17-30 | 15-40 min | + squash, petal folds |
|
| 793 |
-
| High Intermediate | 25-50 | 20-60 min | + sink folds, complex shaping |
|
| 794 |
-
| Complex | 40-100 | 1-4 hours | All fold types, multi-step collapses |
|
| 795 |
-
| Super Complex | 100-1000+ | Hours to weeks | Nested sinks, 10+ simultaneous creases |
|
| 796 |
-
|
| 797 |
-
### 7.2 Model Complexity Examples
|
| 798 |
-
|
| 799 |
-
| Model | Approximate Folds | Level | Key Operations |
|
| 800 |
-
|-------|-------------------|-------|----------------|
|
| 801 |
-
| Paper airplane | 5-7 | Simple | Valley folds only |
|
| 802 |
-
| Fortune teller | 8 | Simple | Valley folds, turn over |
|
| 803 |
-
| Waterbomb (balloon) | 10-12 | Simple | Valley, mountain, inflate |
|
| 804 |
-
| Jumping frog | 15-18 | Low Intermediate | Valley, mountain, pleat |
|
| 805 |
-
| Crane (tsuru) | 30-31 | Intermediate | Valley, petal, inside reverse, inflate |
|
| 806 |
-
| Lily/Iris | 35-45 | Intermediate | Valley, petal, curl |
|
| 807 |
-
| Traditional frog | 40-50 | High Intermediate | Valley, petal, sink, reverse |
|
| 808 |
-
| Dragon | 60-100 | Complex | All fold types |
|
| 809 |
-
| Kawasaki's rose | 40-60 | Complex | Twist folds, curl, 3D shaping |
|
| 810 |
-
| Kamiya's Ryujin 3.5 (dragon) | 1000+ | Super Complex | Everything, including nested sinks |
|
| 811 |
-
|
| 812 |
-
### 7.3 Complexity Scaling
|
| 813 |
-
|
| 814 |
-
The relationship between fold count and model complexity is **super-linear**:
|
| 815 |
-
|
| 816 |
-
- Each fold adds to the **state space** exponentially (more layers, more possible future folds)
|
| 817 |
-
- Compound folds (petal, sink) each involve 3-10 atomic crease changes
|
| 818 |
-
- **Layer count** grows exponentially: after n flat folds, up to 2^n layers in some regions
|
| 819 |
-
- **Branching factor**: At each step, the number of possible next folds depends on the
|
| 820 |
-
current crease pattern, number of exposed edges, and layer configuration
|
| 821 |
-
- **Collapse operations**: Some steps require 4-10+ creases to change simultaneously,
|
| 822 |
-
making them hard to decompose into atomic actions for RL
|
| 823 |
-
|
| 824 |
-
### 7.4 Computational Complexity Results
|
| 825 |
-
|
| 826 |
-
- **Flat-foldability** (single vertex): Polynomial -- checkable via Kawasaki's and Maekawa's theorems
|
| 827 |
-
- **Flat-foldability** (multi-vertex, global): **NP-complete** (proven by Bern and Hayes, 1996)
|
| 828 |
-
- **Simple fold sequences**: Can be verified in polynomial time
|
| 829 |
-
- **Optimal fold sequences** (minimum folds to reach a target): Unknown complexity class,
|
| 830 |
-
likely intractable for complex models
|
| 831 |
-
|
| 832 |
-
---
|
| 833 |
-
|
| 834 |
-
## 8. Flat-Foldability Theorems
|
| 835 |
-
|
| 836 |
-
These theorems constrain what configurations are VALID in the state space -- they define the
|
| 837 |
-
physics/rules of the environment.
|
| 838 |
-
|
| 839 |
-
### 8.1 Kawasaki's Theorem (Kawasaki-Justin Theorem)
|
| 840 |
-
|
| 841 |
-
**At a single flat-foldable vertex, the alternating sum of consecutive sector angles equals zero.**
|
| 842 |
-
|
| 843 |
-
```
|
| 844 |
-
alpha_1 - alpha_2 + alpha_3 - alpha_4 + ... = 0
|
| 845 |
-
```
|
| 846 |
-
|
| 847 |
-
Equivalently: if you partition the angles around a vertex into two alternating subsets,
|
| 848 |
-
each subset sums to exactly 180 degrees.
|
| 849 |
-
|
| 850 |
-
- **Applies to**: Single vertex, flat-foldable patterns
|
| 851 |
-
- **Necessary and sufficient**: For single-vertex flat foldability (combined with Maekawa's)
|
| 852 |
-
- **Discovered by**: Kawasaki, Robertson, and Justin (late 1970s - early 1980s)
|
| 853 |
-
|
| 854 |
-
### 8.2 Maekawa's Theorem (Maekawa-Justin Theorem)
|
| 855 |
-
|
| 856 |
-
**At every flat-foldable vertex, the number of mountain folds and valley folds differ by exactly 2.**
|
| 857 |
-
|
| 858 |
-
```
|
| 859 |
-
|M - V| = 2
|
| 860 |
-
```
|
| 861 |
-
|
| 862 |
-
Where M = number of mountain folds and V = number of valley folds at that vertex.
|
| 863 |
-
|
| 864 |
-
- **Corollary**: The total number of creases at a flat-foldable vertex must be even
|
| 865 |
-
- **Corollary**: Since M - V = +/- 2, and M + V = total creases, you can derive M and V
|
| 866 |
-
given the total number of creases
|
| 867 |
-
|
| 868 |
-
### 8.3 Additional Constraints
|
| 869 |
-
|
| 870 |
-
- **Two-colorability**: A flat-foldable crease pattern divides the paper into regions that
|
| 871 |
-
can be two-colored (like a checkerboard) such that regions sharing a crease get different colors
|
| 872 |
-
- **No self-intersection**: Paper cannot pass through itself during folding
|
| 873 |
-
- **Layer ordering**: At any point in a flat-folded model, the layers must have a consistent
|
| 874 |
-
stacking order that respects fold connectivity
|
| 875 |
-
- **Global flat-foldability**: NP-complete for general crease patterns (Bern & Hayes, 1996)
|
| 876 |
-
|
| 877 |
-
### 8.4 Implications for RL Environment
|
| 878 |
-
|
| 879 |
-
These theorems define the **validity constraints** for the environment:
|
| 880 |
-
- After each fold action, the resulting crease pattern must satisfy Kawasaki's and Maekawa's
|
| 881 |
-
theorems at every vertex for flat foldability
|
| 882 |
-
- The layer ordering must remain consistent (no self-intersection)
|
| 883 |
-
- Invalid actions (violating these constraints) should be either prevented or penalized
|
| 884 |
-
|
| 885 |
-
---
|
| 886 |
-
|
| 887 |
-
## 9. RL Environment Design Implications
|
| 888 |
-
|
| 889 |
-
### 9.1 Action Space Options
|
| 890 |
-
|
| 891 |
-
Based on the research, there are three natural levels for defining the action space:
|
| 892 |
-
|
| 893 |
-
#### Option A: Axiom-Level (Pure Primitives)
|
| 894 |
-
```
|
| 895 |
-
Action = HuzitaJustinAxiom(axiom_number, input_points, input_lines)
|
| 896 |
-
```
|
| 897 |
-
- **Size**: 7 axiom types x continuous parameters = continuous action space
|
| 898 |
-
- **Pros**: Mathematically complete, provably covers all possible single folds
|
| 899 |
-
- **Cons**: Very low-level; compound folds like petal fold require many steps;
|
| 900 |
-
the agent must discover compound operations on its own
|
| 901 |
-
|
| 902 |
-
#### Option B: Named Fold Level (Origami Operations)
|
| 903 |
-
```
|
| 904 |
-
Action = FoldOperation(fold_type, target, parameters)
|
| 905 |
-
```
|
| 906 |
-
- **Size**: ~15-19 fold types x continuous parameters
|
| 907 |
-
- **Pros**: Matches how humans think about origami; compound folds are single actions;
|
| 908 |
-
faster learning due to higher-level primitives
|
| 909 |
-
- **Cons**: Not mathematically minimal; some folds are hard to parameterize (sink folds);
|
| 910 |
-
harder to implement a general simulator
|
| 911 |
-
|
| 912 |
-
#### Option C: Hybrid (Recommended)
|
| 913 |
-
```
|
| 914 |
-
Action = {
|
| 915 |
-
level: [axiom | compound]
|
| 916 |
-
if axiom: (axiom_id, params...)
|
| 917 |
-
if compound: (fold_type, flap_id, params...)
|
| 918 |
-
}
|
| 919 |
-
```
|
| 920 |
-
- **Pros**: Agent can use either low-level or high-level actions; macro-actions speed up
|
| 921 |
-
learning while atomic actions enable novel folds
|
| 922 |
-
- **Cons**: Larger action space; need to implement both levels
|
| 923 |
-
|
| 924 |
-
### 9.2 State Representation
|
| 925 |
-
|
| 926 |
-
The state needs to capture:
|
| 927 |
-
1. **Crease pattern**: Graph of vertices and creases with mountain/valley assignments
|
| 928 |
-
2. **Layer ordering**: At every point, which layers are on top
|
| 929 |
-
3. **3D configuration**: Current fold angles (dihedral angles at each crease)
|
| 930 |
-
4. **Paper boundary**: The current outline of the folded paper
|
| 931 |
-
|
| 932 |
-
### 9.3 Reward Shaping Considerations
|
| 933 |
-
|
| 934 |
-
- **Target matching**: Compare current crease pattern to target model's crease pattern
|
| 935 |
-
- **Base achievement**: Intermediate rewards for reaching known bases
|
| 936 |
-
- **Fold validity**: Penalty for invalid folds (violating Kawasaki/Maekawa)
|
| 937 |
-
- **Efficiency**: Bonus for fewer total folds
|
| 938 |
-
- **Symmetry**: Reward for symmetric fold patterns (most models are symmetric)
|
| 939 |
-
|
| 940 |
-
### 9.4 Key Challenges
|
| 941 |
-
|
| 942 |
-
1. **Continuous action space**: Fold lines are defined by continuous parameters (position, angle)
|
| 943 |
-
2. **Variable-length sequences**: Different models need different numbers of folds (5-1000+)
|
| 944 |
-
3. **State explosion**: Layer count grows exponentially with folds
|
| 945 |
-
4. **Physical constraints**: Paper cannot self-intersect; fold validity is non-trivial to check
|
| 946 |
-
5. **3D reasoning**: Many operations (reverse folds, sinks) require reasoning about 3D geometry
|
| 947 |
-
even though the result is flat
|
| 948 |
-
6. **Simultaneous creases**: Compound folds change 3-10 creases at once -- this is hard to
|
| 949 |
-
decompose for a step-by-step environment
|
| 950 |
-
|
| 951 |
-
### 9.5 Suggested Starting Point
|
| 952 |
-
|
| 953 |
-
For a first implementation, consider:
|
| 954 |
-
- **Action space**: Valley fold + Mountain fold + Turn over + Unfold (4 operations with
|
| 955 |
-
continuous fold-line parameters)
|
| 956 |
-
- **Target**: Simple models only (paper airplane, fortune teller, waterbomb)
|
| 957 |
-
- **State**: 2D crease pattern + layer count map
|
| 958 |
-
- **Expansion path**: Add reverse fold -> squash fold -> petal fold -> sink fold as the
|
| 959 |
-
agent masters simpler operations
|
| 960 |
-
|
| 961 |
-
---
|
| 962 |
-
|
| 963 |
-
## Sources
|
| 964 |
-
|
| 965 |
-
- [Huzita-Justin Axioms - Robert J. Lang](https://langorigami.com/article/huzita-justin-axioms/)
|
| 966 |
-
- [Huzita-Hatori Axioms - Wikipedia](https://en.wikipedia.org/wiki/Huzita%E2%80%93Hatori_axioms)
|
| 967 |
-
- [The Huzita-Justin Axioms - Origami Math](https://orimath.wordpress.com/2021/08/02/the-huzita-hatori-axioms/)
|
| 968 |
-
- [One, Two, and Multi-Fold Origami Axioms - Alperin & Lang](https://langorigami.com/wp-content/uploads/2015/09/o4_multifold_axioms.pdf)
|
| 969 |
-
- [Origami Diagramming Conventions - Robert J. Lang](https://langorigami.com/article/origami-diagramming-conventions/)
|
| 970 |
-
- [Yoshizawa-Randlett System - Wikipedia](https://en.wikipedia.org/wiki/Yoshizawa%E2%80%93Randlett_system)
|
| 971 |
-
- [Fold Hierarchy and Origin of Origami Symbols - British Origami](https://www.britishorigami.org/cp-lister-list/fold-hierarchy-and-origin-of-origami-symbols/)
|
| 972 |
-
- [Origami Bases - Wikibooks](https://en.wikibooks.org/wiki/Origami/Techniques/Model_bases)
|
| 973 |
-
- [Origami Techniques Practice - Wikibooks](https://en.wikibooks.org/wiki/Origami/Techniques/Practice)
|
| 974 |
-
- [Kawasaki's Theorem - Wikipedia](https://en.wikipedia.org/wiki/Kawasaki's_theorem)
|
| 975 |
-
- [Maekawa's Theorem - Wikipedia](https://en.wikipedia.org/wiki/Maekawa's_theorem)
|
| 976 |
-
- [Flat Foldability - Abrashi Origami School](https://abrashiorigami.com/maekawa-justin-and-kawasaki-justin-theorems/)
|
| 977 |
-
- [Mathematics of Paper Folding - Wikipedia](https://en.wikipedia.org/wiki/Mathematics_of_paper_folding)
|
| 978 |
-
- [Miura Fold - Wikipedia](https://en.wikipedia.org/wiki/Miura_fold)
|
| 979 |
-
- [Geometry of Miura-folded Metamaterials - PNAS](https://www.pnas.org/doi/10.1073/pnas.1217998110)
|
| 980 |
-
- [Origami Engineering - Misseroni et al.](https://www.daraio.caltech.edu/publications/Misseroni_et_at_2024.pdf)
|
| 981 |
-
- [Flasher Deployable Arrays - NASA](https://ntrs.nasa.gov/citations/20150004060)
|
| 982 |
-
- [Analysis of Origami Flasher-Inspired Structures - MIT](https://dspace.mit.edu/bitstream/handle/1721.1/156648/bai-janebai-sb-meche-2024-thesis.pdf)
|
| 983 |
-
- [Kresling Origami Mechanics - ScienceDirect](https://www.sciencedirect.com/science/article/abs/pii/S0022509624000966)
|
| 984 |
-
- [Kresling Bistable Behavior - Cai & Deng](https://www.researchgate.net/publication/276173838_Bistable_Behavior_of_the_Cylindrical_Origami_Structure_With_Kresling_Pattern)
|
| 985 |
-
- [Freeform Origami Tessellations - Tomohiro Tachi](https://origami.c.u-tokyo.ac.jp/~tachi/cg/FreeformOrigamiTessellationsTachi2013ASME.pdf)
|
| 986 |
-
- [OrigamiUSA Difficulty Guidelines](https://origamiusa.org/difficulty)
|
| 987 |
-
- [Origami Complexity Framework - ScienceDirect](https://www.sciencedirect.com/science/article/pii/S2095034921000489)
|
| 988 |
-
- [Sink Fold - Abrashi Origami School](https://abrashiorigami.com/sink-fold/)
|
| 989 |
-
- [Swivel Fold - Abrashi Origami School](https://abrashiorigami.com/swivel-fold/)
|
| 990 |
-
- [Petal Fold - Abrashi Origami School](https://abrashiorigami.com/petal-fold/)
|
| 991 |
-
- [Rabbit Ear - Origami Book](https://rabbitear.org/book/origami.html)
|
| 992 |
-
- [From Fold to Function: Dynamic Origami Simulation](https://arxiv.org/html/2511.10580)
|
| 993 |
-
- [Automating Rigid Origami Design - IJCAI](https://www.ijcai.org/proceedings/2023/0645.pdf)
|
| 994 |
-
- [OrigamiSpace: Benchmarking LLMs in Origami](https://arxiv.org/html/2511.18450v1)
|
| 995 |
-
- [Valley and Mountain Folds - British Origami](https://www.britishorigami.org/cp-resource/valley-mountain-folds/)
|
| 996 |
-
- [Origami Crane Tutorial - Origami.me](https://origami.me/crane/)
|
| 997 |
-
- [5 Essential Origami Bases - OrigamiZen](https://origamizen.com/5-essential-origami-bases-every-folder-should-master/)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/materials.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
| 1 |
-
# Material Properties & Stress Simulation
|
| 2 |
-
|
| 3 |
-
## Material Types
|
| 4 |
-
|
| 5 |
-
| Material | Young's Modulus | Thickness | Use Case |
|
| 6 |
-
|----------|----------------|-----------|----------|
|
| 7 |
-
| Standard paper | ~2-4 GPa | 0.1 mm | Baseline origami |
|
| 8 |
-
| Kami (origami paper) | ~2 GPa | 0.07 mm | Thin, easy to fold |
|
| 9 |
-
| **Mylar** | ~3-5 GPa | 0.01-0.1 mm | **Space solar panels** |
|
| 10 |
-
| **Aluminum foil** | ~70 GPa | 0.02 mm | **Deployable structures** |
|
| 11 |
-
| Foil-backed paper | Composite | 0.1-0.2 mm | Metal + paper laminate |
|
| 12 |
-
| Tyvek (synthetic) | ~0.2 GPa | 0.15 mm | Tear-resistant shelters |
|
| 13 |
-
|
| 14 |
-
## How Material Affects the Environment
|
| 15 |
-
|
| 16 |
-
Material properties change:
|
| 17 |
-
- **Fold radius constraint** — thicker/stiffer = larger minimum bend radius
|
| 18 |
-
- **Max layers** — thickness limits how many layers can stack
|
| 19 |
-
- **Stress at creases** — Young's modulus × curvature = stress concentration
|
| 20 |
-
- **Strain tolerance** — each material has a failure threshold
|
| 21 |
-
|
| 22 |
-
## Stress Visualization
|
| 23 |
-
|
| 24 |
-
**Ghassaei's approach (what we port):**
|
| 25 |
-
- Per-vertex strain = avg percent deviation of edge lengths from rest lengths
|
| 26 |
-
- Color map: blue (0 strain) → red (max strain)
|
| 27 |
-
- This is **Cauchy strain / engineering strain**
|
| 28 |
-
|
| 29 |
-
**For RL reward:**
|
| 30 |
-
- `total_strain = mean(per_vertex_strain)` — lower = better physical validity
|
| 31 |
-
- `max_strain` — must stay below material failure threshold
|
| 32 |
-
- `crease_stress = f(fold_angle, thickness, Young's_modulus)` — per-crease penalty
|
| 33 |
-
|
| 34 |
-
## Thickness Considerations
|
| 35 |
-
- Real paper has nonzero thickness → gaps at vertices where layers meet
|
| 36 |
-
- SWOMPS reference: panel=1mm, crease=0.3mm, panel E=2000MPa, crease E=20MPa
|
| 37 |
-
- For engineering apps: offset panels, tapered hinges accommodate thickness
|
| 38 |
-
- Start zero-thickness, add thickness as advanced feature
|
| 39 |
-
|
| 40 |
-
## How to Use in Prompt
|
| 41 |
-
|
| 42 |
-
```
|
| 43 |
-
Material: Mylar (space-grade)
|
| 44 |
-
- Thickness: 0.05mm
|
| 45 |
-
- Young's modulus: 4 GPa
|
| 46 |
-
- Max strain before failure: 3%
|
| 47 |
-
- Max fold layers: 12
|
| 48 |
-
|
| 49 |
-
Sheet: 1m × 1m
|
| 50 |
-
Constraint: Pack into 10cm × 10cm × 5cm
|
| 51 |
-
Must deploy to > 0.8m² area
|
| 52 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/math_physics_deep.md
DELETED
|
@@ -1,1362 +0,0 @@
|
|
| 1 |
-
# Deep Research: Mathematics, Physics, and Engineering Science of Origami
|
| 2 |
-
|
| 3 |
-
> Theoretical foundations for building an origami simulation engine.
|
| 4 |
-
> Compiled from academic literature, computational geometry research, and engineering publications.
|
| 5 |
-
|
| 6 |
-
---
|
| 7 |
-
|
| 8 |
-
## Table of Contents
|
| 9 |
-
|
| 10 |
-
1. [Mathematical Foundations](#1-mathematical-foundations)
|
| 11 |
-
2. [Mechanics / Physics of Folding](#2-mechanics--physics-of-folding)
|
| 12 |
-
3. [Computational Origami Theory](#3-computational-origami-theory)
|
| 13 |
-
4. [Engineering Metrics](#4-engineering-metrics)
|
| 14 |
-
5. [Key References and Sources](#5-key-references-and-sources)
|
| 15 |
-
|
| 16 |
-
---
|
| 17 |
-
|
| 18 |
-
## 1. Mathematical Foundations
|
| 19 |
-
|
| 20 |
-
### 1.1 Flat-Foldability: What Makes a Crease Pattern Flat-Foldable?
|
| 21 |
-
|
| 22 |
-
A crease pattern is **flat-foldable** if it can be folded from a flat sheet of paper into a completely flat (2D) final state. This is the central question of combinatorial origami. A flat fold is formally modeled as a **piecewise isometric map** from a 2D region to itself, equipped with:
|
| 23 |
-
|
| 24 |
-
1. A **crease pattern** (a planar graph embedded in the paper).
|
| 25 |
-
2. A **mountain-valley (MV) assignment** labeling each crease as mountain (M) or valley (V).
|
| 26 |
-
3. A **layer ordering** that specifies which regions of paper lie above which others in the folded state.
|
| 27 |
-
|
| 28 |
-
For a crease pattern to be flat-foldable, ALL of the following must hold simultaneously:
|
| 29 |
-
|
| 30 |
-
- **Local flat-foldability at every vertex** (Kawasaki-Justin condition).
|
| 31 |
-
- **Valid MV assignment** consistent with Maekawa-Justin and Big-Little-Big constraints at every vertex.
|
| 32 |
-
- **Global layer ordering** that is consistent (no paper passes through itself).
|
| 33 |
-
- **No self-intersection** of the paper during the folding motion (for physical realizability).
|
| 34 |
-
|
| 35 |
-
**Critical distinction:** A crease pattern can be *locally* flat-foldable at every vertex yet fail to be *globally* flat-foldable. Local conditions are necessary but not sufficient for multi-vertex patterns.
|
| 36 |
-
|
| 37 |
-
**Two-colorability:** Every flat-foldable crease pattern is **two-colorable** -- the faces of the crease pattern can be colored with two colors such that no two adjacent faces share the same color. This follows from the fact that each crease reverses the orientation of the paper.
|
| 38 |
-
|
| 39 |
-
---
|
| 40 |
-
|
| 41 |
-
### 1.2 Kawasaki-Justin Theorem
|
| 42 |
-
|
| 43 |
-
#### Exact Statement
|
| 44 |
-
|
| 45 |
-
**Kawasaki-Justin Theorem (Single-Vertex Flat-Foldability, Necessary and Sufficient):**
|
| 46 |
-
Let a single vertex in a crease pattern have 2n crease lines meeting at it, creating consecutive sector angles alpha_1, alpha_2, ..., alpha_{2n} (summing to 2*pi). The crease pattern is flat-foldable at this vertex *if and only if*:
|
| 47 |
-
|
| 48 |
-
```
|
| 49 |
-
alpha_1 - alpha_2 + alpha_3 - alpha_4 + ... + alpha_{2n-1} - alpha_{2n} = 0
|
| 50 |
-
```
|
| 51 |
-
|
| 52 |
-
**Equivalent formulation (the 180-degree form):**
|
| 53 |
-
|
| 54 |
-
```
|
| 55 |
-
alpha_1 + alpha_3 + alpha_5 + ... + alpha_{2n-1} = pi (= 180 degrees)
|
| 56 |
-
alpha_2 + alpha_4 + alpha_6 + ... + alpha_{2n} = pi (= 180 degrees)
|
| 57 |
-
```
|
| 58 |
-
|
| 59 |
-
That is, the sum of every other angle equals pi. This form applies when the paper is initially flat (zero angular defect).
|
| 60 |
-
|
| 61 |
-
**Immediate corollary:** The number of crease lines at a flat-foldable vertex must be **even** (2n lines creating 2n sectors).
|
| 62 |
-
|
| 63 |
-
#### History
|
| 64 |
-
|
| 65 |
-
This theorem was discovered independently by:
|
| 66 |
-
- Toshikazu Kawasaki (1989)
|
| 67 |
-
- Stuart Robertson (late 1970s)
|
| 68 |
-
- Jacques Justin (1986)
|
| 69 |
-
|
| 70 |
-
Hence it is properly called the **Kawasaki-Justin** theorem. The necessity was proven by all three; the sufficiency (that the angle condition is also sufficient for a *single* vertex) was first stated explicitly by Thomas Hull (1994).
|
| 71 |
-
|
| 72 |
-
#### Proof Idea
|
| 73 |
-
|
| 74 |
-
**Necessity:** When paper is folded flat, each crease reverses the paper's orientation. Place the first crease along the positive x-axis. In the flat-folded state, the paper accumulates rotation: after the first sector (angle alpha_1) comes the first crease, which reverses orientation. The second sector rotates by alpha_2 in the reversed direction. After traversing all 2n sectors and 2n creases, we must return to the starting direction. The net rotation is:
|
| 75 |
-
|
| 76 |
-
```
|
| 77 |
-
alpha_1 - alpha_2 + alpha_3 - ... - alpha_{2n} = 0
|
| 78 |
-
```
|
| 79 |
-
|
| 80 |
-
If this sum is not zero, the paper cannot close back on itself, so it cannot fold flat.
|
| 81 |
-
|
| 82 |
-
**Sufficiency:** Given a crease pattern satisfying the angle condition, an explicit flat folding can be constructed via an **accordion fold** (crimp fold). Choose index i such that the partial alternating sum is minimized. Starting from sector alpha_{2i+1}, alternately assign mountain and valley folds, placing each angular wedge below previous folds. At each step until the final fold, the accordion fold avoids self-intersection.
|
| 83 |
-
|
| 84 |
-
#### How to Compute
|
| 85 |
-
|
| 86 |
-
For a simulation engine:
|
| 87 |
-
|
| 88 |
-
```python
|
| 89 |
-
def check_kawasaki(angles):
|
| 90 |
-
"""
|
| 91 |
-
angles: list of sector angles [alpha_1, ..., alpha_2n] in radians,
|
| 92 |
-
measured consecutively around the vertex, summing to 2*pi.
|
| 93 |
-
Returns True if Kawasaki-Justin condition is satisfied.
|
| 94 |
-
"""
|
| 95 |
-
if len(angles) % 2 != 0:
|
| 96 |
-
return False # Must have even number of creases
|
| 97 |
-
alternating_sum = sum(a * ((-1)**i) for i, a in enumerate(angles))
|
| 98 |
-
return abs(alternating_sum) < EPSILON
|
| 99 |
-
|
| 100 |
-
# Equivalent check:
|
| 101 |
-
def check_kawasaki_180(angles):
|
| 102 |
-
odd_sum = sum(angles[i] for i in range(0, len(angles), 2))
|
| 103 |
-
even_sum = sum(angles[i] for i in range(1, len(angles), 2))
|
| 104 |
-
return abs(odd_sum - math.pi) < EPSILON and abs(even_sum - math.pi) < EPSILON
|
| 105 |
-
```
|
| 106 |
-
|
| 107 |
-
**For multi-vertex patterns:** Apply Kawasaki-Justin at *every* interior vertex. This gives a necessary condition for local flat-foldability, but is NOT sufficient for global flat-foldability.
|
| 108 |
-
|
| 109 |
-
---
|
| 110 |
-
|
| 111 |
-
### 1.3 Maekawa-Justin Theorem
|
| 112 |
-
|
| 113 |
-
#### Exact Statement
|
| 114 |
-
|
| 115 |
-
**Maekawa-Justin Theorem:** At any interior vertex of a flat-foldable crease pattern, if M is the number of mountain folds and V is the number of valley folds, then:
|
| 116 |
-
|
| 117 |
-
```
|
| 118 |
-
M - V = +/- 2
|
| 119 |
-
```
|
| 120 |
-
|
| 121 |
-
#### Proof Idea
|
| 122 |
-
|
| 123 |
-
Consider the cross-section near a flat-folded vertex. The paper forms a flat polygon when viewed edge-on. Each mountain fold contributes an interior angle of 360 degrees (the paper wraps over), and each valley fold contributes an interior angle of 0 degrees (the paper tucks under), or vice versa depending on viewing side.
|
| 124 |
-
|
| 125 |
-
The sum of interior angles of an n-gon is (n-2) * 180 degrees. With n = M + V creases:
|
| 126 |
-
|
| 127 |
-
```
|
| 128 |
-
0 * V + 360 * M = (M + V - 2) * 180
|
| 129 |
-
360M = 180M + 180V - 360
|
| 130 |
-
180M - 180V = -360
|
| 131 |
-
M - V = -2
|
| 132 |
-
```
|
| 133 |
-
|
| 134 |
-
Viewing from the other side reverses mountain and valley, giving M - V = +2. Therefore |M - V| = 2.
|
| 135 |
-
|
| 136 |
-
**Corollary:** Since M + V = 2n (total creases) and M - V = +/-2, we get M = n+1, V = n-1 (or vice versa). There is always **one more mountain than valley** (or one more valley than mountain).
|
| 137 |
-
|
| 138 |
-
**Corollary:** The total number of creases at a flat-foldable vertex must be even (consistent with Kawasaki-Justin).
|
| 139 |
-
|
| 140 |
-
#### How to Compute
|
| 141 |
-
|
| 142 |
-
```python
|
| 143 |
-
def check_maekawa(assignment):
|
| 144 |
-
"""
|
| 145 |
-
assignment: list of 'M' or 'V' for each crease at a vertex.
|
| 146 |
-
Returns True if Maekawa condition holds.
|
| 147 |
-
"""
|
| 148 |
-
M = assignment.count('M')
|
| 149 |
-
V = assignment.count('V')
|
| 150 |
-
return abs(M - V) == 2
|
| 151 |
-
|
| 152 |
-
def enumerate_maekawa_assignments(n_creases):
|
| 153 |
-
"""
|
| 154 |
-
Generate all MV assignments satisfying Maekawa's theorem.
|
| 155 |
-
n_creases must be even (= 2k for some k).
|
| 156 |
-
Returns assignments with (k+1) mountains and (k-1) valleys, or vice versa.
|
| 157 |
-
"""
|
| 158 |
-
from itertools import combinations
|
| 159 |
-
k = n_creases // 2
|
| 160 |
-
assignments = []
|
| 161 |
-
for n_mountains in [k + 1, k - 1]:
|
| 162 |
-
for mountain_positions in combinations(range(n_creases), n_mountains):
|
| 163 |
-
assignment = ['V'] * n_creases
|
| 164 |
-
for pos in mountain_positions:
|
| 165 |
-
assignment[pos] = 'M'
|
| 166 |
-
assignments.append(tuple(assignment))
|
| 167 |
-
return assignments
|
| 168 |
-
```
|
| 169 |
-
|
| 170 |
-
**Note:** Maekawa's theorem gives an upper bound on valid MV assignments. Not all assignments satisfying M - V = +/-2 are actually flat-foldable; additional constraints (Big-Little-Big, non-crossing) must also be checked.
|
| 171 |
-
|
| 172 |
-
---
|
| 173 |
-
|
| 174 |
-
### 1.4 Big-Little-Big Angle Theorem
|
| 175 |
-
|
| 176 |
-
#### Exact Statement
|
| 177 |
-
|
| 178 |
-
**Big-Little-Big Lemma (BLB):** In a single-vertex crease pattern with sector angles alpha_1, ..., alpha_{2n}, if a sector angle alpha_i is a **strict local minimum** (i.e., alpha_{i-1} > alpha_i < alpha_{i+1}, with indices modulo 2n), then the two crease lines bounding that sector must have **opposite MV parities** -- one must be mountain and the other must be valley.
|
| 179 |
-
|
| 180 |
-
#### Extended Form (Equal Angles)
|
| 181 |
-
|
| 182 |
-
- If a local minimum consists of an **even** number of consecutive equal sector angles, the number of bordering M and V creases must differ by one.
|
| 183 |
-
- If a local minimum consists of an **odd** number of consecutive equal sector angles, the number of bordering M and V creases must be equal.
|
| 184 |
-
|
| 185 |
-
#### Proof Idea
|
| 186 |
-
|
| 187 |
-
If both bounding creases had the same MV parity (both mountain or both valley), the larger flanking sectors would fold onto the same side of the paper, causing the paper to overlap and self-intersect through the small sector. The only way to avoid this collision is to fold one crease as mountain and the other as valley, so the larger sectors fold to opposite sides.
|
| 188 |
-
|
| 189 |
-
More formally: consider the cross-section near the vertex after flat folding. The small sector must be "sandwiched" between its neighbors. If both adjacent creases have the same parity, the two large sectors would stack on the same side, creating a geometric impossibility (paper would need to pass through itself).
|
| 190 |
-
|
| 191 |
-
#### Significance for Simulation
|
| 192 |
-
|
| 193 |
-
The BLB lemma provides a powerful **pruning rule** for MV assignment search:
|
| 194 |
-
|
| 195 |
-
1. Find all local-minimum sectors.
|
| 196 |
-
2. For each, constrain the bounding creases to have opposite MV parity.
|
| 197 |
-
3. This dramatically reduces the search space before attempting full layer-ordering.
|
| 198 |
-
|
| 199 |
-
```python
|
| 200 |
-
def apply_blb_constraints(angles, n_creases):
|
| 201 |
-
"""
|
| 202 |
-
Returns a dict of constraints: {(crease_i, crease_j): 'opposite'}
|
| 203 |
-
for creases bounding local-minimum sectors.
|
| 204 |
-
"""
|
| 205 |
-
constraints = {}
|
| 206 |
-
for i in range(n_creases):
|
| 207 |
-
prev = (i - 1) % n_creases
|
| 208 |
-
next_ = (i + 1) % n_creases
|
| 209 |
-
if angles[i] < angles[prev] and angles[i] < angles[next_]:
|
| 210 |
-
# Creases i and (i+1) % n bound sector i
|
| 211 |
-
c_left = i # crease on the left of sector i
|
| 212 |
-
c_right = (i + 1) % n_creases # crease on the right
|
| 213 |
-
constraints[(c_left, c_right)] = 'opposite'
|
| 214 |
-
return constraints
|
| 215 |
-
```
|
| 216 |
-
|
| 217 |
-
#### Role in Single-Vertex Foldability Algorithm
|
| 218 |
-
|
| 219 |
-
The complete algorithm for single-vertex flat-foldability:
|
| 220 |
-
|
| 221 |
-
1. Check **Kawasaki-Justin**: alternating angle sum = 0.
|
| 222 |
-
2. Enumerate MV assignments satisfying **Maekawa-Justin**: |M - V| = 2.
|
| 223 |
-
3. Filter by **Big-Little-Big**: opposite parity at local-minimum sectors.
|
| 224 |
-
4. For remaining candidates, verify no self-intersection via a **layer ordering** check (crimp-and-uncrimp algorithm).
|
| 225 |
-
|
| 226 |
-
This runs in **polynomial time** for single vertices (O(2^n) candidate assignments, but BLB pruning makes it tractable in practice for typical crease counts).
|
| 227 |
-
|
| 228 |
-
---
|
| 229 |
-
|
| 230 |
-
### 1.5 Layer Ordering Problem
|
| 231 |
-
|
| 232 |
-
#### What Is It?
|
| 233 |
-
|
| 234 |
-
After a crease pattern is folded flat, different regions (facets) of the paper overlap. The **layer ordering** is a function that assigns each facet a position in a vertical stack wherever facets overlap. The ordering must satisfy:
|
| 235 |
-
|
| 236 |
-
1. **Consistency**: If facet A is above B, and B is above C, then A must be above C (transitivity).
|
| 237 |
-
2. **Non-crossing at creases**: Two facets sharing a crease line must be adjacent in the layer order along that crease (no other facet can be sandwiched between them at the crease).
|
| 238 |
-
3. **Taco-taco constraint**: When two creases cross in the folded image, the layers from one crease cannot interleave arbitrarily with layers from the other.
|
| 239 |
-
4. **Taco-tortilla constraint**: A crease folding layers cannot have an unfolded facet blocking its closure.
|
| 240 |
-
|
| 241 |
-
#### Why NP-Hard?
|
| 242 |
-
|
| 243 |
-
**Bern and Hayes (1996)** proved that determining whether a valid layer ordering exists for a given crease pattern with MV assignment is **NP-complete**.
|
| 244 |
-
|
| 245 |
-
**Proof technique**: Reduction from **Not-All-Equal 3-SAT (NAE-3SAT)**:
|
| 246 |
-
- A **pleat** (two parallel creases creating a fold) encodes a binary variable. The layer ordering of the pleat (which side goes on top) represents the variable's truth value.
|
| 247 |
-
- A **clause gadget** is constructed where three pleats intersect. The gadget can be folded flat if and only if the three incoming pleat states are not all equal.
|
| 248 |
-
- NAE-3SAT asks whether a Boolean formula in which each clause requires that its three literals not all have the same value is satisfiable. Since NAE-3SAT is NP-complete, flat-foldability is NP-complete.
|
| 249 |
-
|
| 250 |
-
**Note on proof history**: A gap in the original 1996 proof went undetected for 20 years until Akitaya et al. (2016) repaired and strengthened it.
|
| 251 |
-
|
| 252 |
-
#### Approximation and Tractability
|
| 253 |
-
|
| 254 |
-
- **Fixed-parameter tractable (FPT)**: Flat foldability is FPT parameterized by the **ply** (maximum number of overlapping layers) and the **treewidth** of the cell adjacency graph. Time complexity: O((p!)^{O(w)} * n^2) where p = ply, w = treewidth, n = number of creases.
|
| 255 |
-
- **Simple patterns**: For single-vertex patterns, the cell adjacency graph is a cycle (treewidth 1), so the algorithm is polynomial.
|
| 256 |
-
- **Map folding (1D)**: Folding a 1D strip of paper with prescribed creases is solvable in O(n^2) time, but the 2D map folding problem remains open for general m x n grids.
|
| 257 |
-
- **Heuristic approaches**: In practice, constraint propagation (Jason Ku's algorithm) works well:
|
| 258 |
-
1. Determine local layer orders from MV assignments at each crease.
|
| 259 |
-
2. Propagate implications (transitivity).
|
| 260 |
-
3. Recursively guess-and-check remaining ambiguities.
|
| 261 |
-
4. Backtrack on contradiction.
|
| 262 |
-
|
| 263 |
-
---
|
| 264 |
-
|
| 265 |
-
### 1.6 Gaussian Curvature in Origami
|
| 266 |
-
|
| 267 |
-
#### Fundamental Property
|
| 268 |
-
|
| 269 |
-
Paper is a **developable surface**: it has **zero Gaussian curvature** everywhere. Gaussian curvature is the product of the two principal curvatures (K = kappa_1 * kappa_2). For paper:
|
| 270 |
-
- At smooth (unfolded) points: the paper can bend in one direction but not two simultaneously, so at least one principal curvature is zero, giving K = 0.
|
| 271 |
-
- At crease lines: the paper has a discontinuity in the surface normal (a "fold"), but the Gaussian curvature of each face on either side remains zero. The crease itself concentrates curvature into a 1D line.
|
| 272 |
-
- At vertices: this is where things get interesting.
|
| 273 |
-
|
| 274 |
-
#### Why Zero Everywhere Except at Vertices
|
| 275 |
-
|
| 276 |
-
The **Gauss-Bonnet theorem** applied to origami: for a closed region of paper containing a vertex, the integral of Gaussian curvature over the region equals the **angular defect** at the vertex:
|
| 277 |
-
|
| 278 |
-
```
|
| 279 |
-
integral(K dA) = 2*pi - sum(sector_angles_at_vertex)
|
| 280 |
-
```
|
| 281 |
-
|
| 282 |
-
For a flat-foldable vertex (Kawasaki-Justin satisfied), the sector angles sum to 2*pi, so the angular defect is zero. The vertex has zero **discrete Gaussian curvature** -- consistent with the paper being developable.
|
| 283 |
-
|
| 284 |
-
For a **non-flat** origami vertex (like a partially folded vertex), the angular defect can be nonzero. This is equivalent to the vertex having concentrated Gaussian curvature, which means the paper near that vertex is behaving like a **cone** (positive defect) or a **saddle** (negative defect). However, this is only possible if the paper is being stretched or compressed at the molecular level -- real paper resists this strongly.
|
| 285 |
-
|
| 286 |
-
**Practical implication for simulation**: The faces of an origami model must remain **developable** (zero Gaussian curvature) at all times. Any deformation that introduces Gaussian curvature into a face is physically unrealistic unless the paper is being plastically deformed. This is why faces in rigid origami are modeled as perfectly rigid panels with zero curvature.
|
| 287 |
-
|
| 288 |
-
#### Connection to Rigid Origami
|
| 289 |
-
|
| 290 |
-
In rigid origami, faces must remain planar throughout folding. If triangulated faces are used, planarity is automatic (any three points define a plane). For non-triangular faces, enforcing planarity is a constraint that must be explicitly maintained, and violation of it implies non-zero Gaussian curvature -- physically forbidden for stiff materials.
|
| 291 |
-
|
| 292 |
-
---
|
| 293 |
-
|
| 294 |
-
### 1.7 Rigid Origami
|
| 295 |
-
|
| 296 |
-
#### Definition
|
| 297 |
-
|
| 298 |
-
**Rigid origami** is origami where:
|
| 299 |
-
- Each face of the crease pattern remains **perfectly rigid** (planar, undeformed) during folding.
|
| 300 |
-
- All deformation is concentrated at the crease lines, which act as **revolute joints** (hinges).
|
| 301 |
-
- The folding is a **continuous motion** from the flat state to the folded state.
|
| 302 |
-
|
| 303 |
-
This is in contrast to "regular" origami where paper can bend, curve, and deform continuously.
|
| 304 |
-
|
| 305 |
-
#### What Makes It Different
|
| 306 |
-
|
| 307 |
-
| Property | Regular Origami | Rigid Origami |
|
| 308 |
-
|----------|----------------|---------------|
|
| 309 |
-
| Faces | Can bend and curve | Must remain perfectly planar |
|
| 310 |
-
| Deformation | Distributed across paper | Concentrated at creases only |
|
| 311 |
-
| Degrees of freedom | Very high (infinite-dimensional) | Finite (one fold angle per crease) |
|
| 312 |
-
| Model | Continuous thin shell | Linkage mechanism |
|
| 313 |
-
| Kinematic analogy | Flexible sheet | System of rigid panels + hinges |
|
| 314 |
-
| Gaussian curvature of faces | Can be nonzero transiently | Always zero |
|
| 315 |
-
|
| 316 |
-
#### Kinematic Model
|
| 317 |
-
|
| 318 |
-
A rigid origami vertex with n creases is equivalent to a **spherical linkage** with n links and n revolute joints. The fold angles (rho_1, ..., rho_n) are the joint variables. The constraint is that the linkage must close:
|
| 319 |
-
|
| 320 |
-
```
|
| 321 |
-
R_1(rho_1) * R_2(rho_2) * ... * R_n(rho_n) = I (identity matrix)
|
| 322 |
-
```
|
| 323 |
-
|
| 324 |
-
where R_i(rho_i) is the rotation matrix for the i-th crease by fold angle rho_i around the crease line direction.
|
| 325 |
-
|
| 326 |
-
For a **degree-4 vertex** (4 creases, the most common in rigid origami patterns like Miura-ori), the spherical linkage has **1 degree of freedom** (generically). This means specifying one fold angle determines all others -- the entire pattern folds as a 1-DOF mechanism.
|
| 327 |
-
|
| 328 |
-
#### Key Rigid Origami Patterns
|
| 329 |
-
|
| 330 |
-
- **Miura-ori**: Degree-4 vertices, 1-DOF, negative Poisson's ratio, widely used in engineering.
|
| 331 |
-
- **Yoshizawa pattern**: Various fold angles possible.
|
| 332 |
-
- **Waterbomb base**: Degree-6 vertices, more complex kinematics.
|
| 333 |
-
- **Kresling pattern**: Cylindrical, bistable, used in deployable structures.
|
| 334 |
-
|
| 335 |
-
#### Rigid Foldability Conditions
|
| 336 |
-
|
| 337 |
-
Not all crease patterns are rigidly foldable. Conditions:
|
| 338 |
-
1. **Kawasaki-Justin** must hold at every vertex (necessary for flat-foldability).
|
| 339 |
-
2. The **loop closure equations** (spherical linkage constraints) must have continuous solutions from the flat state.
|
| 340 |
-
3. **Compatibility**: At each interior vertex, the fold angles of shared creases between adjacent vertices must agree.
|
| 341 |
-
|
| 342 |
-
For degree-4 vertices, explicit analytical conditions exist. For higher-degree vertices and multi-vertex patterns, numerical methods are required.
|
| 343 |
-
|
| 344 |
-
---
|
| 345 |
-
|
| 346 |
-
### 1.8 Single-Vertex Origami
|
| 347 |
-
|
| 348 |
-
This is the **simplest and fully understood** case of origami mathematics.
|
| 349 |
-
|
| 350 |
-
#### Configuration Space
|
| 351 |
-
|
| 352 |
-
For a single vertex with 2n creases satisfying Kawasaki-Justin, the **configuration space** (set of valid folded states) is fully characterized:
|
| 353 |
-
|
| 354 |
-
- **MV assignments**: The number of valid MV assignments can be computed by recursive formulas (Hull). For n creases with sector angles alpha_1, ..., alpha_{2n}, the count C(alpha_0, ..., alpha_{2n-1}) follows a recursion based on identifying the smallest sector and "crimping" it.
|
| 355 |
-
|
| 356 |
-
- **Layer orderings**: For each valid MV assignment, the layer ordering is essentially unique (determined by the accordion fold construction from the sufficiency proof of Kawasaki-Justin).
|
| 357 |
-
|
| 358 |
-
#### Complete Algorithm (Polynomial Time)
|
| 359 |
-
|
| 360 |
-
```
|
| 361 |
-
Input: sector angles alpha_1, ..., alpha_{2n}
|
| 362 |
-
1. Check Kawasaki-Justin: alternating sum = 0?
|
| 363 |
-
If no -> not flat-foldable.
|
| 364 |
-
2. For each candidate MV assignment satisfying Maekawa (|M-V| = 2):
|
| 365 |
-
a. Check Big-Little-Big constraints.
|
| 366 |
-
b. Simulate crimp folding to verify no self-intersection.
|
| 367 |
-
3. Output all valid (MV assignment, layer ordering) pairs.
|
| 368 |
-
```
|
| 369 |
-
|
| 370 |
-
This is solvable in **polynomial time** because:
|
| 371 |
-
- Maekawa limits the number of mountain folds to n+1 or n-1 (binomial choices).
|
| 372 |
-
- BLB dramatically prunes the search.
|
| 373 |
-
- The crimp algorithm processes creases sequentially.
|
| 374 |
-
|
| 375 |
-
#### Rigid Single-Vertex
|
| 376 |
-
|
| 377 |
-
For rigid origami, a single degree-2n vertex is a spherical n-bar linkage. Its configuration space is a smooth manifold whose dimension and topology depend on the sector angles. For degree-4 (the generic case), it is generically a 1-dimensional manifold (a curve) -- a single parameter (fold angle of one crease) determines the rest.
|
| 378 |
-
|
| 379 |
-
---
|
| 380 |
-
|
| 381 |
-
### 1.9 Multi-Vertex Origami: Where Complexity Explodes
|
| 382 |
-
|
| 383 |
-
#### The Fundamental Difficulty
|
| 384 |
-
|
| 385 |
-
Multi-vertex origami is qualitatively harder than single-vertex because:
|
| 386 |
-
|
| 387 |
-
1. **Global consistency**: Creases shared between vertices must have consistent fold angles and MV assignments at both endpoints.
|
| 388 |
-
2. **Layer ordering becomes global**: The layer ordering must be consistent across the entire pattern, not just locally at each vertex.
|
| 389 |
-
3. **Rigid compatibility**: In rigid origami, the fold angles at one vertex constrain fold angles at neighboring vertices, creating a coupled system of nonlinear equations.
|
| 390 |
-
|
| 391 |
-
#### Complexity Results (Multi-Vertex)
|
| 392 |
-
|
| 393 |
-
| Problem | Complexity |
|
| 394 |
-
|---------|-----------|
|
| 395 |
-
| Local flat-foldability (Kawasaki at each vertex) | Polynomial (check each vertex independently) |
|
| 396 |
-
| MV assignment for flat fold | **NP-complete** (Bern-Hayes 1996) |
|
| 397 |
-
| Layer ordering for flat fold | **NP-complete** (Bern-Hayes 1996) |
|
| 398 |
-
| Flat foldability (combined) | **NP-complete** |
|
| 399 |
-
| Rigid foldability (continuous motion) | **NP-complete** for arbitrary crease subsets |
|
| 400 |
-
| Reconfiguration between flat states | **PSPACE-complete** |
|
| 401 |
-
| Counting flat-folded states | **#P-complete** |
|
| 402 |
-
| Self-similar infinite patterns | **Undecidable** |
|
| 403 |
-
| Flat origami with optional creases | **Turing complete** (can simulate Rule 110) |
|
| 404 |
-
|
| 405 |
-
#### Why Multi-Vertex is Hard: Intuition
|
| 406 |
-
|
| 407 |
-
Each vertex locally constrains its creases, but these constraints propagate through the pattern. A pleat at one vertex forces a layer ordering that may conflict with a pleat at a distant vertex. The interconnection structure creates a constraint satisfaction problem equivalent to SAT.
|
| 408 |
-
|
| 409 |
-
**Bern-Hayes gadgets**:
|
| 410 |
-
- **Wire gadget**: A pleat (two parallel creases) propagates a binary signal (which layer is on top).
|
| 411 |
-
- **NOT gadget**: A single crease crossing a pleat flips the signal.
|
| 412 |
-
- **NAE clause gadget**: Three pleats meeting at a region that can fold flat iff the signals are not all equal.
|
| 413 |
-
- These gadgets encode NAE-3SAT, proving NP-completeness.
|
| 414 |
-
|
| 415 |
-
#### Practical Approaches for Simulation
|
| 416 |
-
|
| 417 |
-
Despite NP-completeness in the worst case, many practical origami patterns are tractable:
|
| 418 |
-
|
| 419 |
-
1. **Low ply**: Most artistic origami has bounded ply (< 10 layers). FPT algorithms with ply as parameter are efficient.
|
| 420 |
-
2. **Tree-like structure**: Many patterns have cell adjacency graphs with low treewidth, enabling dynamic programming.
|
| 421 |
-
3. **Constraint propagation**: Start from boundary conditions and propagate MV/layer constraints. Many patterns resolve without backtracking.
|
| 422 |
-
4. **Physics-based simulation**: Avoid the combinatorial problem entirely by simulating the continuous folding process with forces and constraints.
|
| 423 |
-
|
| 424 |
-
---
|
| 425 |
-
|
| 426 |
-
## 2. Mechanics / Physics of Folding
|
| 427 |
-
|
| 428 |
-
### 2.1 What Physically Happens When Paper Is Folded
|
| 429 |
-
|
| 430 |
-
Paper is a composite material: a network of **cellulose fibers** (typically 1-3 mm long, 20-40 micrometers in diameter) bonded together by hydrogen bonds, with pores filled with air, fillers (clay, calcium carbonate), and sizing agents.
|
| 431 |
-
|
| 432 |
-
#### Microscale Mechanics of a Fold
|
| 433 |
-
|
| 434 |
-
When paper is folded:
|
| 435 |
-
|
| 436 |
-
1. **Elastic bending** (initial phase): The outer fibers are stretched in tension, inner fibers compressed. At the fold line, the paper experiences its maximum curvature. The bending stress follows approximately:
|
| 437 |
-
```
|
| 438 |
-
sigma = E * y / R
|
| 439 |
-
```
|
| 440 |
-
where E = Young's modulus, y = distance from neutral axis, R = radius of curvature.
|
| 441 |
-
|
| 442 |
-
2. **Fiber rearrangement**: At moderate fold sharpness, fibers begin to slide relative to each other, breaking hydrogen bonds between fibers. This is an irreversible microstructural change.
|
| 443 |
-
|
| 444 |
-
3. **Plastic deformation** (sharp fold): For a tight crease, the inner fibers buckle and permanently deform. The outer fibers may fracture or debond. The cellulose microfibrils within individual fibers undergo plastic kinking. The fold line experiences **localized plastic failure**.
|
| 445 |
-
|
| 446 |
-
4. **Residual stress**: After unfolding, the permanently deformed fibers create internal stresses that give the paper a "memory" of the fold -- it preferentially returns to the folded state. This is the physical basis of crease memory.
|
| 447 |
-
|
| 448 |
-
#### Key Parameters
|
| 449 |
-
|
| 450 |
-
| Parameter | Typical Value (copy paper) |
|
| 451 |
-
|-----------|---------------------------|
|
| 452 |
-
| Thickness | 80-120 micrometers |
|
| 453 |
-
| Young's modulus (MD) | 3-6 GPa |
|
| 454 |
-
| Young's modulus (CD) | 1-3 GPa |
|
| 455 |
-
| Bending stiffness | 5-15 mN*m |
|
| 456 |
-
| Tensile strength | 20-80 MPa |
|
| 457 |
-
| Strain at break | 1-5% |
|
| 458 |
-
|
| 459 |
-
MD = machine direction, CD = cross direction. Paper is anisotropic due to preferential fiber alignment during manufacturing.
|
| 460 |
-
|
| 461 |
-
---
|
| 462 |
-
|
| 463 |
-
### 2.2 Stress Concentration at Fold Lines
|
| 464 |
-
|
| 465 |
-
#### The Problem
|
| 466 |
-
|
| 467 |
-
A crease line is a **stress concentrator**. The radius of curvature at a sharp fold approaches zero, which would imply infinite stress according to linear elasticity:
|
| 468 |
-
|
| 469 |
-
```
|
| 470 |
-
sigma_max = E * t / (2R)
|
| 471 |
-
```
|
| 472 |
-
|
| 473 |
-
where t = paper thickness, R = radius of curvature at the fold.
|
| 474 |
-
|
| 475 |
-
In reality, three mechanisms prevent infinite stress:
|
| 476 |
-
1. **Finite fold radius**: Even a "sharp" fold has a radius of curvature of ~0.1-0.5 mm.
|
| 477 |
-
2. **Plastic yielding**: The material yields before reaching the elastic stress limit.
|
| 478 |
-
3. **Fiber debonding**: The network structure allows local failures that redistribute stress.
|
| 479 |
-
|
| 480 |
-
#### Modeling Approach
|
| 481 |
-
|
| 482 |
-
For simulation purposes, the stress concentration at a fold is typically not modeled explicitly. Instead:
|
| 483 |
-
|
| 484 |
-
1. **Ideal crease model**: The crease is a geometric line of zero width. All deformation is captured by the fold angle. The crease has a **rest angle** (the angle it naturally tends toward) and a **torsional stiffness** (resistance to deviating from the rest angle).
|
| 485 |
-
|
| 486 |
-
2. **Compliant crease model**: The crease has a finite width w. Within this width, the paper has reduced stiffness (due to plastic damage). The crease zone is modeled as a thin strip with modified material properties.
|
| 487 |
-
|
| 488 |
-
3. **Damage model**: The crease stiffness changes as a function of folding history. Repeated folding reduces the rest angle toward the fully-folded state and may reduce stiffness.
|
| 489 |
-
|
| 490 |
-
---
|
| 491 |
-
|
| 492 |
-
### 2.3 Elastic Energy Stored in a Fold
|
| 493 |
-
|
| 494 |
-
The elastic energy of an origami structure comes from three sources:
|
| 495 |
-
|
| 496 |
-
#### 1. Crease Folding Energy
|
| 497 |
-
|
| 498 |
-
Each crease acts as a **torsional spring**. The energy stored in a crease of length L with fold angle rho (deviation from rest angle rho_0):
|
| 499 |
-
|
| 500 |
-
```
|
| 501 |
-
E_crease = (1/2) * kappa * L * (rho - rho_0)^2
|
| 502 |
-
```
|
| 503 |
-
|
| 504 |
-
where kappa is the torsional stiffness per unit length (units: N*m/m = N).
|
| 505 |
-
|
| 506 |
-
For a crease at its rest angle, E_crease = 0. Energy increases quadratically as the fold angle deviates from rest.
|
| 507 |
-
|
| 508 |
-
#### 2. Panel Bending Energy
|
| 509 |
-
|
| 510 |
-
If panels are not perfectly rigid, they can bend. The bending energy density of a thin plate is:
|
| 511 |
-
|
| 512 |
-
```
|
| 513 |
-
e_bend = (B/2) * (kappa_1^2 + kappa_2^2 + 2*nu*kappa_1*kappa_2)
|
| 514 |
-
```
|
| 515 |
-
|
| 516 |
-
where B = Et^3/(12(1-nu^2)) is the flexural rigidity, kappa_1 and kappa_2 are principal curvatures, and nu is Poisson's ratio.
|
| 517 |
-
|
| 518 |
-
For a developable (zero Gaussian curvature) deformation, one principal curvature is zero, simplifying to:
|
| 519 |
-
|
| 520 |
-
```
|
| 521 |
-
e_bend = (B/2) * kappa^2
|
| 522 |
-
```
|
| 523 |
-
|
| 524 |
-
#### 3. Stretching Energy
|
| 525 |
-
|
| 526 |
-
If panels are stretched or compressed in-plane:
|
| 527 |
-
|
| 528 |
-
```
|
| 529 |
-
e_stretch = (Et/2) * (epsilon_xx^2 + epsilon_yy^2 + 2*nu*epsilon_xx*epsilon_yy + 2*(1-nu)*epsilon_xy^2) / (1 - nu^2)
|
| 530 |
-
```
|
| 531 |
-
|
| 532 |
-
In rigid origami, stretching energy is zero by assumption. In compliant origami, it is typically much smaller than bending energy for thin sheets.
|
| 533 |
-
|
| 534 |
-
#### The Key Ratio: Origami Length Scale
|
| 535 |
-
|
| 536 |
-
The competition between panel bending and crease folding defines a characteristic length:
|
| 537 |
-
|
| 538 |
-
```
|
| 539 |
-
L* = B / kappa
|
| 540 |
-
```
|
| 541 |
-
|
| 542 |
-
where B = flexural rigidity of the panel, kappa = torsional stiffness of the crease per unit length.
|
| 543 |
-
|
| 544 |
-
- If panel size >> L*: the creases are relatively soft, and the structure behaves like **rigid origami** (panels stay flat, all deformation at creases).
|
| 545 |
-
- If panel size << L*: the creases are relatively stiff, and the panels bend significantly -- the structure behaves as a **flexible shell**.
|
| 546 |
-
|
| 547 |
-
For most paper origami, panel size >> L*, so rigid origami is a good approximation.
|
| 548 |
-
|
| 549 |
-
---
|
| 550 |
-
|
| 551 |
-
### 2.4 Bending Stiffness vs. Fold Angle Relationship
|
| 552 |
-
|
| 553 |
-
The moment-angle relationship of a crease is approximately **linear** for small deviations from the rest angle:
|
| 554 |
-
|
| 555 |
-
```
|
| 556 |
-
M = kappa * L * (rho - rho_0)
|
| 557 |
-
```
|
| 558 |
-
|
| 559 |
-
where M is the restoring moment, L is crease length, rho is current fold angle, rho_0 is rest angle.
|
| 560 |
-
|
| 561 |
-
**Nonlinear effects at large deviations:**
|
| 562 |
-
|
| 563 |
-
1. **Geometric nonlinearity**: For large fold angles, the effective lever arm changes, and the relationship between moment and angle becomes nonlinear.
|
| 564 |
-
2. **Material nonlinearity**: At large deformations, the crease material (damaged paper fibers) exhibits nonlinear stress-strain behavior.
|
| 565 |
-
3. **Contact**: When the fold approaches 0 degrees (fully folded) or 360 degrees, the paper layers contact each other, introducing a hard constraint.
|
| 566 |
-
|
| 567 |
-
**For simulation**, a piecewise model is often used:
|
| 568 |
-
|
| 569 |
-
```
|
| 570 |
-
M(rho) = kappa * L * (rho - rho_0) for |rho - rho_0| < rho_threshold
|
| 571 |
-
M(rho) = kappa * L * rho_threshold * sign(rho - rho_0) for |rho - rho_0| >= rho_threshold
|
| 572 |
-
```
|
| 573 |
-
|
| 574 |
-
More sophisticated models use exponential or polynomial stiffening near full closure.
|
| 575 |
-
|
| 576 |
-
**Accuracy Note**: The linear spring model is suggested for use when the folding angle deviation is less than approximately 45 degrees from rest. Beyond this range, nonlinear corrections become significant.
|
| 577 |
-
|
| 578 |
-
---
|
| 579 |
-
|
| 580 |
-
### 2.5 Crease Mechanics: Permanent Effects
|
| 581 |
-
|
| 582 |
-
A crease permanently alters paper properties:
|
| 583 |
-
|
| 584 |
-
#### Rest Angle Shift (Plasticity)
|
| 585 |
-
|
| 586 |
-
Before folding: rest angle = pi (flat, 180 degrees).
|
| 587 |
-
After folding to angle rho_fold and releasing:
|
| 588 |
-
|
| 589 |
-
```
|
| 590 |
-
rho_0_new = pi - alpha * (pi - rho_fold)
|
| 591 |
-
```
|
| 592 |
-
|
| 593 |
-
where alpha is a plasticity parameter (0 = no plastic deformation, 1 = perfect memory of the fold). For sharp folds in standard paper, alpha is approximately 0.7-0.9.
|
| 594 |
-
|
| 595 |
-
The rest angle shifts toward the folded state. This is why folded paper stays folded -- the physical basis of origami.
|
| 596 |
-
|
| 597 |
-
#### Stiffness Reduction
|
| 598 |
-
|
| 599 |
-
The torsional stiffness of a crease decreases with repeated folding:
|
| 600 |
-
|
| 601 |
-
```
|
| 602 |
-
kappa_n = kappa_0 * (1 - beta * log(n + 1))
|
| 603 |
-
```
|
| 604 |
-
|
| 605 |
-
where kappa_n is stiffness after n fold cycles, kappa_0 is initial stiffness, and beta is a material-dependent degradation parameter.
|
| 606 |
-
|
| 607 |
-
This models the progressive breaking of fiber bonds with repeated folding.
|
| 608 |
-
|
| 609 |
-
#### Anisotropy
|
| 610 |
-
|
| 611 |
-
A crease introduces local anisotropy: the paper is much weaker in bending across the crease line than along it. In-plane stiffness across the crease is also reduced.
|
| 612 |
-
|
| 613 |
-
---
|
| 614 |
-
|
| 615 |
-
### 2.6 Thickness Accommodation
|
| 616 |
-
|
| 617 |
-
#### The Problem
|
| 618 |
-
|
| 619 |
-
Mathematical origami assumes zero-thickness paper. Real materials have thickness t > 0, which creates problems:
|
| 620 |
-
|
| 621 |
-
1. **Geometric interference**: When paper folds, the inner layers have shorter paths than outer layers. For a fold angle theta, the path length difference is approximately t * theta.
|
| 622 |
-
2. **Accumulation**: In multi-layer folds, thickness accumulates, causing significant geometric errors.
|
| 623 |
-
3. **Strain**: Thick materials experience significant bending strain at folds: epsilon = t/(2R).
|
| 624 |
-
4. **Kinematics**: Thick panels cannot fold the same way as zero-thickness paper -- the kinematics change.
|
| 625 |
-
|
| 626 |
-
#### Thickness Accommodation Techniques
|
| 627 |
-
|
| 628 |
-
**1. Offset Panel Technique:**
|
| 629 |
-
Panels are offset from the mathematical (zero-thickness) fold surface by t/2. Each panel sits on one side of the mathematical surface. Creases are placed at the edges of offset panels, creating gaps.
|
| 630 |
-
|
| 631 |
-
- Preserves kinematics of the zero-thickness pattern.
|
| 632 |
-
- Introduces gaps between panels at valley folds.
|
| 633 |
-
- Simple to implement but creates asymmetry.
|
| 634 |
-
|
| 635 |
-
**2. Tapered Panel Technique:**
|
| 636 |
-
Panels are tapered (thinned) near crease lines, maintaining the mathematical surface at the center of each panel while allowing folding at the edges.
|
| 637 |
-
|
| 638 |
-
- Preserves the same kinematic folding motion as the zero-thickness system.
|
| 639 |
-
- Requires material removal.
|
| 640 |
-
- Suitable for manufacturing.
|
| 641 |
-
|
| 642 |
-
**3. Double Crease Technique:**
|
| 643 |
-
A single mathematical crease is replaced by two parallel creases separated by a distance proportional to thickness. The strip between them accommodates the volume.
|
| 644 |
-
|
| 645 |
-
- Simple and effective.
|
| 646 |
-
- Changes the crease pattern geometry slightly.
|
| 647 |
-
- Common in engineering applications.
|
| 648 |
-
|
| 649 |
-
**4. Membrane Hinge Technique:**
|
| 650 |
-
Thick panels are connected by thin flexible membranes (living hinges). The membrane serves as the hinge while the panels remain rigid and thick.
|
| 651 |
-
|
| 652 |
-
- Clean separation of rigid panels and flexible hinges.
|
| 653 |
-
- Widely used in manufactured products (e.g., plastic containers with living hinges).
|
| 654 |
-
- The membrane can be different material from the panels.
|
| 655 |
-
|
| 656 |
-
**5. Rolling Contact Technique:**
|
| 657 |
-
Panels are connected by rolling contact elements that accommodate thickness through synchronized rolling motion.
|
| 658 |
-
|
| 659 |
-
- Achieves true rigid foldability with thick panels.
|
| 660 |
-
- Complex mechanism but kinematically exact.
|
| 661 |
-
|
| 662 |
-
---
|
| 663 |
-
|
| 664 |
-
### 2.7 Spring-Hinge Model
|
| 665 |
-
|
| 666 |
-
The **spring-hinge model** is the simplest mechanical model of origami:
|
| 667 |
-
|
| 668 |
-
#### Model Description
|
| 669 |
-
|
| 670 |
-
- Each **face** of the crease pattern is a rigid panel.
|
| 671 |
-
- Each **crease** is a **torsional spring** (revolute joint with spring resistance).
|
| 672 |
-
- The spring has:
|
| 673 |
-
- A **rest angle** rho_0 (the angle the crease naturally tends toward).
|
| 674 |
-
- A **torsional stiffness** kappa (resistance to deviation from rest angle).
|
| 675 |
-
- A **damping coefficient** c (energy dissipation during dynamic motion).
|
| 676 |
-
|
| 677 |
-
#### Equations of Motion
|
| 678 |
-
|
| 679 |
-
For a dynamic simulation:
|
| 680 |
-
|
| 681 |
-
```
|
| 682 |
-
I * d^2(rho)/dt^2 + c * d(rho)/dt + kappa * (rho - rho_0) = tau_external
|
| 683 |
-
```
|
| 684 |
-
|
| 685 |
-
where I is the moment of inertia, c is damping, kappa is torsional stiffness, and tau_external is any externally applied torque.
|
| 686 |
-
|
| 687 |
-
For quasi-static simulation (slow folding), inertia is negligible:
|
| 688 |
-
|
| 689 |
-
```
|
| 690 |
-
c * d(rho)/dt + kappa * (rho - rho_0) = tau_external
|
| 691 |
-
```
|
| 692 |
-
|
| 693 |
-
#### Advantages
|
| 694 |
-
|
| 695 |
-
- Simple and computationally efficient.
|
| 696 |
-
- Captures the essential 1-DOF-per-crease kinematics.
|
| 697 |
-
- Easy to implement.
|
| 698 |
-
|
| 699 |
-
#### Limitations
|
| 700 |
-
|
| 701 |
-
- Cannot capture face bending (faces are perfectly rigid).
|
| 702 |
-
- Cannot capture stretching or shearing.
|
| 703 |
-
- Crease stiffness is a single scalar (no width, no distributed behavior).
|
| 704 |
-
- Poor accuracy for compliant origami where face bending is significant.
|
| 705 |
-
|
| 706 |
-
---
|
| 707 |
-
|
| 708 |
-
### 2.8 Bar-and-Hinge Model (Ghassaei's Approach)
|
| 709 |
-
|
| 710 |
-
The **bar-and-hinge model** is a more sophisticated mechanical model that captures three types of deformation while remaining computationally efficient.
|
| 711 |
-
|
| 712 |
-
#### Model Description
|
| 713 |
-
|
| 714 |
-
Each face of the crease pattern is triangulated (if not already triangular). The model then consists of:
|
| 715 |
-
|
| 716 |
-
1. **Bars (edges)**: Each edge of the triangulated mesh is a bar with **axial stiffness**. Bars resist stretching and compression. This prevents the faces from stretching or shearing.
|
| 717 |
-
|
| 718 |
-
2. **Fold hinges (at creases)**: Each crease line has a **torsional spring** connecting the two adjacent faces. This captures the crease folding behavior with a target fold angle and stiffness.
|
| 719 |
-
|
| 720 |
-
3. **Facet hinges (within faces)**: Each pair of triangles sharing a non-crease edge has a **torsional spring** with high stiffness and a rest angle of pi (flat). This penalizes face bending, keeping faces approximately flat while allowing small deformations.
|
| 721 |
-
|
| 722 |
-
#### Three Deformation Modes
|
| 723 |
-
|
| 724 |
-
| Mode | Element | Stiffness | Physical meaning |
|
| 725 |
-
|------|---------|-----------|-----------------|
|
| 726 |
-
| Stretching/shearing | Bars | Axial stiffness (Et) | In-plane deformation of faces |
|
| 727 |
-
| Face bending | Facet hinges | Flexural rigidity (B ~ Et^3) | Out-of-plane bending of faces |
|
| 728 |
-
| Crease folding | Fold hinges | Torsional stiffness (kappa) | Folding at prescribed crease lines |
|
| 729 |
-
|
| 730 |
-
#### Energy Formulation
|
| 731 |
-
|
| 732 |
-
Total energy:
|
| 733 |
-
|
| 734 |
-
```
|
| 735 |
-
E_total = E_bar + E_facet + E_fold
|
| 736 |
-
|
| 737 |
-
E_bar = sum_bars (1/2) * k_axial * (L - L_0)^2
|
| 738 |
-
E_facet = sum_facets (1/2) * k_facet * l * (theta - pi)^2
|
| 739 |
-
E_fold = sum_folds (1/2) * k_fold * l * (rho - rho_target)^2
|
| 740 |
-
```
|
| 741 |
-
|
| 742 |
-
where L, L_0 = current and rest length of bars; theta = dihedral angle at facet edges; rho, rho_target = current and target fold angle at creases; l = edge length; and k values are stiffness parameters.
|
| 743 |
-
|
| 744 |
-
#### Stiffness Parameters
|
| 745 |
-
|
| 746 |
-
From Schenk and Guest's formulation:
|
| 747 |
-
|
| 748 |
-
```
|
| 749 |
-
k_axial = E * t * w / L_0 (bar axial stiffness)
|
| 750 |
-
k_facet = E * t^3 / (12 * (1-nu^2)) (per unit length, facet bending)
|
| 751 |
-
k_fold = kappa (per unit length, crease torsion)
|
| 752 |
-
```
|
| 753 |
-
|
| 754 |
-
where E = Young's modulus, t = thickness, w = tributary width, nu = Poisson's ratio.
|
| 755 |
-
|
| 756 |
-
#### Compliant Crease Extension
|
| 757 |
-
|
| 758 |
-
For creases with finite width w_c, Ghassaei et al. extended the model:
|
| 759 |
-
- The crease zone is represented by **7 nodes, 12 bars, and 8 rotational springs**.
|
| 760 |
-
- Two rows of rotational springs (not one) capture the distributed curvature in the crease zone.
|
| 761 |
-
- Additional bars capture torsional and extensional deformation within the crease.
|
| 762 |
-
|
| 763 |
-
This extension is critical for predicting **bistability** and **multistability** in origami structures, which the simplified model misses.
|
| 764 |
-
|
| 765 |
-
#### Solution Method
|
| 766 |
-
|
| 767 |
-
The bar-and-hinge model is solved using **nonlinear static analysis**:
|
| 768 |
-
|
| 769 |
-
1. Define target fold angles (rho_target) as a function of a folding parameter t in [0, 1].
|
| 770 |
-
2. Increment t in small steps.
|
| 771 |
-
3. At each step, minimize E_total using Newton-Raphson iteration:
|
| 772 |
-
- Compute forces: F_i = -partial(E_total)/partial(x_i)
|
| 773 |
-
- Compute stiffness matrix: K_ij = partial^2(E_total)/partial(x_i)partial(x_j)
|
| 774 |
-
- Update positions: delta_x = K^{-1} * F
|
| 775 |
-
4. Check convergence.
|
| 776 |
-
|
| 777 |
-
#### GPU-Accelerated Dynamic Variant (Origami Simulator)
|
| 778 |
-
|
| 779 |
-
Amanda Ghassaei's browser-based Origami Simulator uses a dynamic variant:
|
| 780 |
-
- All constraints (bars, facet hinges, fold hinges) generate forces.
|
| 781 |
-
- Positions are updated via numerical integration (Euler or Verlet).
|
| 782 |
-
- **Verlet integration**: x_{n+1} = 2*x_n - x_{n-1} + F*dt^2/m
|
| 783 |
-
- GPU fragment shaders compute forces in parallel for fast performance.
|
| 784 |
-
- Damping prevents oscillation; system converges to static equilibrium.
|
| 785 |
-
- All creases fold simultaneously (not sequentially).
|
| 786 |
-
|
| 787 |
-
---
|
| 788 |
-
|
| 789 |
-
## 3. Computational Origami Theory
|
| 790 |
-
|
| 791 |
-
### 3.1 Tractability Landscape
|
| 792 |
-
|
| 793 |
-
| Problem | Complexity | Notes |
|
| 794 |
-
|---------|-----------|-------|
|
| 795 |
-
| Single-vertex flat foldability (Kawasaki check) | **O(n)** | Sum alternating angles |
|
| 796 |
-
| Single-vertex MV assignment | **Polynomial** | Recursive crimp algorithm |
|
| 797 |
-
| Single-vertex layer ordering | **Polynomial** | Determined by MV assignment |
|
| 798 |
-
| Multi-vertex local flat foldability | **Polynomial** | Check Kawasaki at each vertex independently |
|
| 799 |
-
| Multi-vertex MV assignment | **NP-complete** | Bern-Hayes 1996 |
|
| 800 |
-
| Multi-vertex layer ordering | **NP-complete** | Bern-Hayes 1996 |
|
| 801 |
-
| Multi-vertex flat foldability (combined) | **NP-complete** | MV + layer ordering together |
|
| 802 |
-
| Flat foldability (bounded ply + bounded treewidth) | **FPT**: O((p!)^{O(w)} n^2) | Tractable for simple patterns |
|
| 803 |
-
| Map folding (1xn strip) | **O(n^2)** | |
|
| 804 |
-
| Map folding (2xn grid) | **Polynomial** | |
|
| 805 |
-
| Map folding (general mxn) | **Open** | Conjectured NP-hard |
|
| 806 |
-
| Rigid foldability (continuous motion, all creases) | **Weakly NP-complete** | |
|
| 807 |
-
| Rigid foldability (arbitrary crease subsets) | **Strongly NP-complete** | |
|
| 808 |
-
| Reconfiguration between flat states | **PSPACE-complete** | |
|
| 809 |
-
| Counting flat-folded states | **#P-complete** | |
|
| 810 |
-
| Flat origami with optional creases | **Turing complete** | Simulates Rule 110 cellular automaton |
|
| 811 |
-
| Self-similar infinite pattern foldability | **Undecidable** | coRE-complete |
|
| 812 |
-
|
| 813 |
-
### 3.2 Single-Vertex Flat Foldability: Polynomial Time
|
| 814 |
-
|
| 815 |
-
**Algorithm (Hull's Crimp Algorithm):**
|
| 816 |
-
|
| 817 |
-
```
|
| 818 |
-
Input: Sector angles alpha_1, ..., alpha_{2n} at a vertex.
|
| 819 |
-
Output: All valid (MV assignment, layer ordering) pairs, or "not flat-foldable."
|
| 820 |
-
|
| 821 |
-
1. Check Kawasaki-Justin: sum of odd-indexed angles = pi?
|
| 822 |
-
If not, return "not flat-foldable."
|
| 823 |
-
|
| 824 |
-
2. Find the smallest sector angle alpha_min.
|
| 825 |
-
(If tie, choose any.)
|
| 826 |
-
|
| 827 |
-
3. The two creases bounding alpha_min must have OPPOSITE MV parity
|
| 828 |
-
(Big-Little-Big lemma).
|
| 829 |
-
|
| 830 |
-
4. "Crimp" the smallest sector: merge it with the adjacent sectors.
|
| 831 |
-
The new sector has angle = alpha_{i-1} - alpha_i + alpha_{i+1}
|
| 832 |
-
(which equals the original angle alpha_{i-1} or alpha_{i+1} minus the "crimped" difference).
|
| 833 |
-
|
| 834 |
-
5. This reduces the vertex to a (2n-2)-crease vertex.
|
| 835 |
-
Recurse.
|
| 836 |
-
|
| 837 |
-
6. Base case: 2 creases, angles pi, pi. Trivially foldable.
|
| 838 |
-
```
|
| 839 |
-
|
| 840 |
-
**Time complexity:** O(n^2) in the straightforward implementation, O(n log n) with priority queues.
|
| 841 |
-
|
| 842 |
-
**Counting valid MV assignments:** Hull developed recursive formulas C(alpha_0,...,alpha_{2n-1}) for the number of valid MV assignments, based on the crimp structure. The count depends on angle multiplicities and can range from 2 (minimum, for generic angles) to 2^{n-1} (maximum, for equal angles).
|
| 843 |
-
|
| 844 |
-
### 3.3 Multi-Vertex Flat Foldability: NP-Complete
|
| 845 |
-
|
| 846 |
-
**The Bern-Hayes Construction (1996):**
|
| 847 |
-
|
| 848 |
-
The reduction is from **NAE-3SAT** (Not-All-Equal 3-Satisfiability):
|
| 849 |
-
- Given a Boolean formula in CNF where each clause has 3 literals.
|
| 850 |
-
- NAE-3SAT asks: is there an assignment such that no clause has all three literals the same value?
|
| 851 |
-
|
| 852 |
-
**Gadgets:**
|
| 853 |
-
|
| 854 |
-
1. **Wire (pleat)**: Two parallel creases forming a pleat. Binary state = which layer goes on top. This encodes a Boolean variable.
|
| 855 |
-
|
| 856 |
-
2. **Signal propagation**: The pleat's layer ordering propagates along the crease lines, maintaining the binary state.
|
| 857 |
-
|
| 858 |
-
3. **Turn/crossover**: Pleats can turn corners and cross over each other using specific crease configurations.
|
| 859 |
-
|
| 860 |
-
4. **NAE clause gadget**: Three pleats meeting at a junction. The junction can fold flat if and only if the three incoming layer orderings are NOT all equal. This directly encodes an NAE-3SAT clause.
|
| 861 |
-
|
| 862 |
-
5. **Assembly**: Given an NAE-3SAT formula, construct a crease pattern with one pleat per variable and one clause gadget per clause, connected by wire gadgets. The crease pattern is flat-foldable iff the NAE-3SAT formula is satisfiable.
|
| 863 |
-
|
| 864 |
-
Since NAE-3SAT is NP-complete, and the reduction is polynomial, multi-vertex flat foldability is NP-complete.
|
| 865 |
-
|
| 866 |
-
**Implications for simulation**: There is no known polynomial-time algorithm for deciding flat-foldability of general crease patterns (unless P = NP). Practical simulation must use heuristics, constraint propagation, or physics-based approaches.
|
| 867 |
-
|
| 868 |
-
### 3.4 Rigid Foldability: How to Check
|
| 869 |
-
|
| 870 |
-
#### Degree-4 Vertices (Analytical)
|
| 871 |
-
|
| 872 |
-
For a degree-4 vertex with sector angles (alpha, beta, gamma, delta) where alpha + beta + gamma + delta = 2*pi and Kawasaki-Justin holds (alpha + gamma = beta + delta = pi):
|
| 873 |
-
|
| 874 |
-
The fold angles (rho_1, rho_2, rho_3, rho_4) are related by analytical expressions. For a Miura-ori type vertex:
|
| 875 |
-
|
| 876 |
-
```
|
| 877 |
-
tan(rho_2/2) = -(cos((alpha-beta)/2) / cos((alpha+beta)/2)) * tan(rho_1/2)
|
| 878 |
-
tan(rho_3/2) = -(cos((alpha+delta)/2) / cos((alpha-delta)/2)) * tan(rho_1/2)
|
| 879 |
-
tan(rho_4/2) = -(cos((alpha-beta)/2) / cos((alpha+beta)/2)) * tan(rho_3/2)
|
| 880 |
-
```
|
| 881 |
-
|
| 882 |
-
These give explicit fold-angle relationships, showing the 1-DOF nature.
|
| 883 |
-
|
| 884 |
-
#### General Vertices (Numerical)
|
| 885 |
-
|
| 886 |
-
For a vertex of degree n, the rigid foldability condition is the **loop closure equation** of the equivalent spherical linkage:
|
| 887 |
-
|
| 888 |
-
```
|
| 889 |
-
R(e_1, rho_1) * R(e_2, rho_2) * ... * R(e_n, rho_n) = I
|
| 890 |
-
```
|
| 891 |
-
|
| 892 |
-
where R(e_i, rho_i) is rotation by angle rho_i around axis e_i (the crease direction).
|
| 893 |
-
|
| 894 |
-
This is a system of nonlinear equations in the fold angles. The number of degrees of freedom is n - 3 (for a spherical linkage with n links).
|
| 895 |
-
|
| 896 |
-
**Solution methods:**
|
| 897 |
-
1. **Newton-Raphson**: Iteratively solve the constraint equations starting from the flat state.
|
| 898 |
-
2. **Continuation methods**: Trace the solution curve as a parameter (e.g., one fold angle) varies.
|
| 899 |
-
3. **Dual quaternion method**: Model rotations using dual quaternions for improved numerical stability. The QRS method decomposes fold angles into quaternion representations for multi-vertex systems.
|
| 900 |
-
|
| 901 |
-
#### Multi-Vertex Rigid Foldability
|
| 902 |
-
|
| 903 |
-
For a pattern with multiple vertices:
|
| 904 |
-
1. Each vertex imposes loop closure constraints.
|
| 905 |
-
2. Shared creases between vertices must have the same fold angle.
|
| 906 |
-
3. The combined system of constraints defines the configuration space.
|
| 907 |
-
|
| 908 |
-
**Heuristic algorithms** (e.g., by Tachi) adjust vertex positions of initially non-rigid patterns to make them rigidly foldable. The configuration is represented by fold angles, and the folding trajectory is computed by projecting the desired motion onto the constraint manifold.
|
| 909 |
-
|
| 910 |
-
### 3.5 Self-Intersection Detection
|
| 911 |
-
|
| 912 |
-
Self-intersection detection is critical for validating origami configurations. Two types:
|
| 913 |
-
|
| 914 |
-
#### 1. Discrete Self-Intersection (Flat Folds)
|
| 915 |
-
|
| 916 |
-
For flat-folded states, self-intersection is detected by checking **layer ordering consistency**:
|
| 917 |
-
- **Taco-taco constraint**: Two crossing creases create a "taco-taco" configuration. The layer ordering of the four resulting regions must be consistent (no interleaving that requires paper to pass through itself).
|
| 918 |
-
- **Taco-tortilla constraint**: A crease adjacent to a non-creased region must not have the flat region blocking its closure.
|
| 919 |
-
- **Transitivity**: If A > B and B > C in layer order, then A > C.
|
| 920 |
-
|
| 921 |
-
Jason Ku's algorithm:
|
| 922 |
-
1. Compute the overlap graph (which facets overlap in the folded state).
|
| 923 |
-
2. For each pair of overlapping facets, determine ordering constraints from MV assignments at shared boundaries.
|
| 924 |
-
3. Propagate implications via transitivity.
|
| 925 |
-
4. Check for contradictions (cycles in the ordering).
|
| 926 |
-
|
| 927 |
-
#### 2. Continuous Self-Intersection (During Folding)
|
| 928 |
-
|
| 929 |
-
During the folding motion, faces may collide transiently even if the final state is valid.
|
| 930 |
-
|
| 931 |
-
**Approaches:**
|
| 932 |
-
- **Bounding volume hierarchies (BVH)**: Enclose each face in a bounding box. Test for overlap using a BVH tree. If bounding boxes overlap, test the actual triangles for intersection.
|
| 933 |
-
- **Swept volume**: Track the volume swept by each face during a folding step. Check for overlaps between swept volumes.
|
| 934 |
-
- **Incremental testing**: At each time step, check all face pairs for geometric intersection using triangle-triangle intersection tests (Moller's algorithm or similar).
|
| 935 |
-
|
| 936 |
-
**Computational cost**: O(n^2) per time step for brute-force pairwise testing, O(n log n) with spatial acceleration structures.
|
| 937 |
-
|
| 938 |
-
### 3.6 Fold-and-Cut Theorem
|
| 939 |
-
|
| 940 |
-
#### Statement
|
| 941 |
-
|
| 942 |
-
**Fold-and-Cut Theorem (Demaine, Demaine, Lubiw, 1998):** Every pattern of straight-line cuts in a flat piece of paper (i.e., any planar straight-line graph drawn on the paper) can be achieved by folding the paper flat and making a single complete straight cut.
|
| 943 |
-
|
| 944 |
-
In other words: any shape (or collection of shapes) bounded by straight edges can be cut from a single sheet of paper with one straight cut, provided the paper is first folded appropriately.
|
| 945 |
-
|
| 946 |
-
#### Two Proof Methods
|
| 947 |
-
|
| 948 |
-
**1. Straight Skeleton Method (Demaine, Demaine, Lubiw):**
|
| 949 |
-
- Compute the **straight skeleton** of the planar graph (a medial-axis-like structure formed by shrinking the faces of the graph at unit speed).
|
| 950 |
-
- The straight skeleton edges become crease lines.
|
| 951 |
-
- Add perpendicular creases to make the pattern flat-foldable.
|
| 952 |
-
- The resulting crease pattern, when folded, aligns all cut edges along a single line, enabling the single cut.
|
| 953 |
-
|
| 954 |
-
**2. Disk Packing Method (Bern, Demaine, Eppstein, Hayes):**
|
| 955 |
-
- Pack disks into the faces of the graph, tangent to each edge.
|
| 956 |
-
- The **Apollonius problem** (finding circles tangent to three given circles) is used to fill gaps.
|
| 957 |
-
- The disk packing defines a decomposition into **molecules** (regions between disks).
|
| 958 |
-
- **Universal molecules** are constructed for each region, providing crease patterns that fold each molecule flat.
|
| 959 |
-
- The assembly of all molecules gives the complete crease pattern.
|
| 960 |
-
|
| 961 |
-
#### Universal Molecule
|
| 962 |
-
|
| 963 |
-
A **universal molecule** is a crease pattern for a convex polygonal region with prescribed fold directions on its boundary such that:
|
| 964 |
-
1. It folds flat.
|
| 965 |
-
2. All boundary edges align along a single line after folding.
|
| 966 |
-
|
| 967 |
-
The universal molecule construction uses:
|
| 968 |
-
- The **perpendicular folds** from each boundary edge.
|
| 969 |
-
- **Rabbit ear** triangulations for triangular regions.
|
| 970 |
-
- **Recursive decomposition** for higher polygons.
|
| 971 |
-
|
| 972 |
-
This is a key primitive for computational origami design tools.
|
| 973 |
-
|
| 974 |
-
### 3.7 Universal Molecule Approach
|
| 975 |
-
|
| 976 |
-
The universal molecule approach is the computational backbone of the fold-and-cut construction and is more broadly used in computational origami design.
|
| 977 |
-
|
| 978 |
-
**Algorithm:**
|
| 979 |
-
|
| 980 |
-
1. **Input**: A planar straight-line graph G (the desired crease/cut pattern).
|
| 981 |
-
2. **Compute the straight skeleton** of G. This produces a tree-like structure inside each face.
|
| 982 |
-
3. **Decompose** the pattern into convex regions using the skeleton.
|
| 983 |
-
4. **For each convex region**, construct a universal molecule:
|
| 984 |
-
a. Assign fold directions (M/V) on the boundary based on the desired folding.
|
| 985 |
-
b. Add internal creases: perpendicular folds from vertices, diagonal folds for triangulation.
|
| 986 |
-
c. Verify flat-foldability of the molecule.
|
| 987 |
-
5. **Assemble** all molecules. The internal creases of adjacent molecules must be compatible.
|
| 988 |
-
6. **Output**: Complete crease pattern with MV assignment.
|
| 989 |
-
|
| 990 |
-
**Implementation challenges:**
|
| 991 |
-
- The straight skeleton can have degenerate cases (vertices of degree > 3).
|
| 992 |
-
- Disk packing requires solving Apollonius problems (circle tangent to three circles), which has up to 8 solutions per instance.
|
| 993 |
-
- Numerical precision is critical: small errors in skeleton computation propagate to invalid crease patterns.
|
| 994 |
-
- Software tools like **TreeMaker** (Robert Lang) and **ORIPA** implement variants of this approach.
|
| 995 |
-
|
| 996 |
-
---
|
| 997 |
-
|
| 998 |
-
## 4. Engineering Metrics
|
| 999 |
-
|
| 1000 |
-
### 4.1 Deployment Ratio
|
| 1001 |
-
|
| 1002 |
-
#### Definition
|
| 1003 |
-
|
| 1004 |
-
The **deployment ratio** (or **packaging ratio**) quantifies how much an origami structure can expand from its folded (stowed) state to its deployed state:
|
| 1005 |
-
|
| 1006 |
-
```
|
| 1007 |
-
DR = L_deployed / L_stowed
|
| 1008 |
-
```
|
| 1009 |
-
|
| 1010 |
-
where L is a characteristic dimension (length, area, or volume, depending on application).
|
| 1011 |
-
|
| 1012 |
-
**Variants:**
|
| 1013 |
-
|
| 1014 |
-
- **Linear deployment ratio**: ratio of deployed length to stowed length (1D).
|
| 1015 |
-
- **Areal deployment ratio**: ratio of deployed area to stowed area (2D): DR_area = A_deployed / A_stowed.
|
| 1016 |
-
- **Volumetric deployment ratio**: ratio of deployed volume to stowed volume (3D).
|
| 1017 |
-
|
| 1018 |
-
#### How It's Measured in Real Engineering
|
| 1019 |
-
|
| 1020 |
-
1. **Satellite solar arrays**: Deployment ratio measured as deployed array area divided by launch fairing cross-section area. Typical: 10:1 to 50:1 for advanced designs.
|
| 1021 |
-
2. **Antenna reflectors**: Ratio of deployed aperture diameter to stowed package diameter. Typical: 5:1 to 20:1.
|
| 1022 |
-
3. **Emergency shelters**: Ratio of floor area deployed to storage volume. Typical: 20:1 to 100:1.
|
| 1023 |
-
|
| 1024 |
-
#### Theoretical Limits
|
| 1025 |
-
|
| 1026 |
-
For a Miura-ori pattern with m x n cells:
|
| 1027 |
-
```
|
| 1028 |
-
DR_linear ~ m * sin(theta) (in one direction)
|
| 1029 |
-
DR_area ~ m * n * sin(theta) (area ratio)
|
| 1030 |
-
```
|
| 1031 |
-
where theta is the fold angle parameter. As theta approaches 0, the deployment ratio increases, but the stowed package thickness also increases due to layer accumulation.
|
| 1032 |
-
|
| 1033 |
-
**Practical limit**: Deployment ratio is bounded by the number of layers (ply) and the material thickness. For n layers of thickness t, the minimum stowed dimension is ~ n*t.
|
| 1034 |
-
|
| 1035 |
-
### 4.2 Structural Stiffness of Folded State
|
| 1036 |
-
|
| 1037 |
-
Origami structures exhibit **tunable stiffness** that depends on the fold state:
|
| 1038 |
-
|
| 1039 |
-
#### Stiffness Modulation
|
| 1040 |
-
|
| 1041 |
-
- **Flat (unfolded) state**: Maximum in-plane stiffness, minimum out-of-plane stiffness (it is just a flat sheet).
|
| 1042 |
-
- **Partially folded**: Stiffness varies continuously with fold angle. The structure gains out-of-plane stiffness (3D geometry provides structural depth) while losing some in-plane stiffness.
|
| 1043 |
-
- **Fully folded (compact)**: Complex stiffness behavior depending on pattern; often the stiffest configuration due to maximum layer stacking.
|
| 1044 |
-
|
| 1045 |
-
#### Miura-ori Stiffness
|
| 1046 |
-
|
| 1047 |
-
The effective in-plane stiffness of a Miura-ori pattern scales as:
|
| 1048 |
-
|
| 1049 |
-
```
|
| 1050 |
-
K_x ~ E*t * (cos(theta)^2 / sin(theta)) (along corrugation direction)
|
| 1051 |
-
K_y ~ E*t * sin(theta) (perpendicular to corrugation)
|
| 1052 |
-
```
|
| 1053 |
-
|
| 1054 |
-
where theta is the fold angle. Note that K_x diverges as the fold closes (theta -> 0), while K_y vanishes -- this is the auxetic behavior.
|
| 1055 |
-
|
| 1056 |
-
The **out-of-plane bending stiffness** scales approximately as:
|
| 1057 |
-
|
| 1058 |
-
```
|
| 1059 |
-
B_eff ~ E*t * h^2
|
| 1060 |
-
```
|
| 1061 |
-
|
| 1062 |
-
where h is the effective structural depth (related to fold amplitude).
|
| 1063 |
-
|
| 1064 |
-
#### Self-Locking
|
| 1065 |
-
|
| 1066 |
-
Some origami patterns exhibit **self-locking**: at certain fold angles, geometric constraints prevent further motion without significant force. The structure transitions from a mechanism (zero-stiffness mode) to a structure (finite stiffness in all directions).
|
| 1067 |
-
|
| 1068 |
-
### 4.3 Fatigue at Fold Lines
|
| 1069 |
-
|
| 1070 |
-
#### The Problem
|
| 1071 |
-
|
| 1072 |
-
Repeated folding and unfolding causes progressive damage at crease lines:
|
| 1073 |
-
|
| 1074 |
-
1. **Cycle 1**: Initial plastic deformation creates the crease. Fiber bonds break, fibers permanently deform.
|
| 1075 |
-
2. **Cycles 2-10**: Crease softens, rest angle shifts further. The crease becomes "trained."
|
| 1076 |
-
3. **Cycles 10-100**: Gradual stiffness degradation. Fiber fracture accumulates.
|
| 1077 |
-
4. **Cycles 100-1000**: Significant weakening. Risk of crack propagation from the crease into the faces.
|
| 1078 |
-
5. **Cycles 1000+**: Material failure. The paper tears along the fold line.
|
| 1079 |
-
|
| 1080 |
-
#### Fatigue Life
|
| 1081 |
-
|
| 1082 |
-
The fatigue life depends on:
|
| 1083 |
-
- **Material**: Paper (100-1000 cycles), polymers (10^4-10^6), metals (10^3-10^5), shape memory alloys (10^6+).
|
| 1084 |
-
- **Fold angle amplitude**: Larger angle changes = faster fatigue.
|
| 1085 |
-
- **Fold radius**: Sharper folds = higher strain = faster fatigue.
|
| 1086 |
-
- **Loading rate**: Faster folding = more heat generation = accelerated degradation.
|
| 1087 |
-
|
| 1088 |
-
#### Modeling Fatigue
|
| 1089 |
-
|
| 1090 |
-
For simulation, a simple fatigue model:
|
| 1091 |
-
|
| 1092 |
-
```
|
| 1093 |
-
kappa_N = kappa_0 * (1 - D(N))
|
| 1094 |
-
D(N) = (N / N_f)^p
|
| 1095 |
-
|
| 1096 |
-
where:
|
| 1097 |
-
kappa_N = stiffness after N cycles
|
| 1098 |
-
kappa_0 = initial stiffness
|
| 1099 |
-
N_f = cycles to failure
|
| 1100 |
-
p = material exponent (typically 0.5-2)
|
| 1101 |
-
D = damage variable (0 = undamaged, 1 = failed)
|
| 1102 |
-
```
|
| 1103 |
-
|
| 1104 |
-
When D(N) = 1, the crease has failed (torn through).
|
| 1105 |
-
|
| 1106 |
-
#### Design for Fatigue Resistance
|
| 1107 |
-
|
| 1108 |
-
- **Living hinges**: Use a different, more fatigue-resistant material at the crease (e.g., polypropylene).
|
| 1109 |
-
- **Wider creases**: Distribute the deformation over a wider zone, reducing peak strain.
|
| 1110 |
-
- **Reduced fold amplitude**: Design for partial folding rather than full 180-degree folds.
|
| 1111 |
-
- **Material selection**: Elastomers and thermoplastic elastomers offer the best fatigue resistance.
|
| 1112 |
-
|
| 1113 |
-
### 4.4 Bistability
|
| 1114 |
-
|
| 1115 |
-
#### Definition
|
| 1116 |
-
|
| 1117 |
-
An origami structure is **bistable** if it has two distinct stable equilibrium configurations separated by an energy barrier. The structure can "snap" between the two states.
|
| 1118 |
-
|
| 1119 |
-
#### Mechanism
|
| 1120 |
-
|
| 1121 |
-
Bistability arises from the competition between:
|
| 1122 |
-
1. **Crease energy**: Creases prefer their rest angles.
|
| 1123 |
-
2. **Face bending energy**: Faces resist bending.
|
| 1124 |
-
3. **Geometric constraints**: The topology of the pattern constrains the configuration space.
|
| 1125 |
-
|
| 1126 |
-
When the energy landscape has two local minima (valleys) separated by a saddle point (hill), the structure is bistable.
|
| 1127 |
-
|
| 1128 |
-
#### Example: Kresling Pattern
|
| 1129 |
-
|
| 1130 |
-
The Kresling pattern (a cylindrical origami pattern) is naturally bistable:
|
| 1131 |
-
- **State 1**: Extended (tall cylinder).
|
| 1132 |
-
- **State 2**: Compressed (short, twisted cylinder).
|
| 1133 |
-
- **Transition**: Requires overcoming an energy barrier (the faces must bend temporarily during the snap-through).
|
| 1134 |
-
|
| 1135 |
-
The bistability of the Kresling pattern can be tuned by adjusting:
|
| 1136 |
-
- Number of sides (polygon order).
|
| 1137 |
-
- Fold angle of the pattern.
|
| 1138 |
-
- Material stiffness ratio (crease vs. face).
|
| 1139 |
-
|
| 1140 |
-
#### Modeling Bistability
|
| 1141 |
-
|
| 1142 |
-
The bar-and-hinge model with compliant creases (Ghassaei's extended model) can capture bistability. The key is:
|
| 1143 |
-
1. Include face bending energy (facet hinges), not just crease folding energy.
|
| 1144 |
-
2. Use the compliant crease model (finite crease width) to capture torsional and extensional deformation in the crease zone.
|
| 1145 |
-
3. Trace the energy landscape as a function of a loading parameter to find multiple stable equilibria.
|
| 1146 |
-
|
| 1147 |
-
**Critical**: The simplified spring-hinge model (rigid faces + torsional springs at creases) typically CANNOT predict bistability because it lacks face bending energy. The competition between face and crease energies is essential for bistability.
|
| 1148 |
-
|
| 1149 |
-
### 4.5 Shape Memory in Origami Structures
|
| 1150 |
-
|
| 1151 |
-
#### Crease-Level Shape Memory
|
| 1152 |
-
|
| 1153 |
-
Individual creases exhibit shape memory due to plastic deformation:
|
| 1154 |
-
- After folding and unfolding, the crease "remembers" the fold angle.
|
| 1155 |
-
- Upon re-folding, the crease preferentially returns to its previously folded state.
|
| 1156 |
-
- The memory improves with repeated folding cycles.
|
| 1157 |
-
|
| 1158 |
-
This is the basis of the **Miura-ori map**: a pre-folded Miura-ori pattern can be unfolded to flat and easily re-folded by pushing two opposite corners together. The crease memory guides the paper back to the correct folded state.
|
| 1159 |
-
|
| 1160 |
-
#### Material-Level Shape Memory
|
| 1161 |
-
|
| 1162 |
-
**Shape memory alloys (SMAs)** and **shape memory polymers (SMPs)** can be used to create origami structures with active shape memory:
|
| 1163 |
-
|
| 1164 |
-
- **SMA origami**: Creases made from nitinol wire. At low temperature, the wire is flexible and the structure can be folded flat. Upon heating, the SMA transitions to its austenite phase and contracts, deploying the structure to its memorized 3D shape.
|
| 1165 |
-
- **SMP origami**: The entire sheet is a shape memory polymer. Heated above T_g, the polymer is soft and can be folded. Cooled below T_g, the polymer hardens in the folded state. Re-heating above T_g triggers autonomous deployment to the flat (or programmed 3D) state.
|
| 1166 |
-
|
| 1167 |
-
#### For Simulation
|
| 1168 |
-
|
| 1169 |
-
Shape memory can be modeled by making the rest angle a function of temperature:
|
| 1170 |
-
|
| 1171 |
-
```
|
| 1172 |
-
rho_0(T) = rho_programmed for T < T_transition
|
| 1173 |
-
rho_0(T) = rho_deployed for T > T_transition
|
| 1174 |
-
rho_0(T) = interpolation in transition zone
|
| 1175 |
-
```
|
| 1176 |
-
|
| 1177 |
-
### 4.6 Auxetic Behavior (Negative Poisson's Ratio)
|
| 1178 |
-
|
| 1179 |
-
#### Definition
|
| 1180 |
-
|
| 1181 |
-
A material or structure has **auxetic** behavior if it exhibits a **negative Poisson's ratio**: when stretched in one direction, it expands in the perpendicular direction (instead of contracting as normal materials do).
|
| 1182 |
-
|
| 1183 |
-
#### Origami Auxetic Patterns
|
| 1184 |
-
|
| 1185 |
-
**Miura-ori** is the canonical example of an auxetic origami pattern:
|
| 1186 |
-
|
| 1187 |
-
```
|
| 1188 |
-
nu_xy = -1 (Poisson's ratio for in-plane deformation of Miura-ori)
|
| 1189 |
-
```
|
| 1190 |
-
|
| 1191 |
-
When a Miura-ori sheet is pulled in the x-direction (along the corrugation), it also expands in the y-direction. This is a purely geometric effect arising from the kinematics of the fold pattern.
|
| 1192 |
-
|
| 1193 |
-
**Physical explanation**: As the Miura-ori unfolds in one direction, the fold angle changes globally (it is a 1-DOF mechanism). This global angle change causes the pattern to simultaneously unfold in the perpendicular direction.
|
| 1194 |
-
|
| 1195 |
-
#### Tunable Poisson's Ratio
|
| 1196 |
-
|
| 1197 |
-
By modifying the pattern geometry, the Poisson's ratio can be tuned:
|
| 1198 |
-
|
| 1199 |
-
- **Standard Miura-ori**: nu = -1 (isotropic auxetic in the folding mode).
|
| 1200 |
-
- **Modified Miura-ori** (varying parallelogram angles): nu can range from negative to positive.
|
| 1201 |
-
- **Reentrant patterns** (Tachi-Miura polyhedra, zigzag modifications): can achieve strongly negative Poisson's ratios (nu < -1).
|
| 1202 |
-
- **Hybrid patterns**: Combining different unit cells can create programmable Poisson's ratio fields.
|
| 1203 |
-
|
| 1204 |
-
#### Engineering Applications
|
| 1205 |
-
|
| 1206 |
-
1. **Impact absorption**: Auxetic structures densify under impact (all directions compress simultaneously), making them excellent energy absorbers.
|
| 1207 |
-
2. **Morphing surfaces**: Auxetic sheets can conform to doubly-curved surfaces (saddle shapes) without wrinkling, because the negative Poisson's ratio accommodates the required in-plane deformation.
|
| 1208 |
-
3. **Deployable structures**: The simultaneous expansion in all directions enables compact packaging and uniform deployment.
|
| 1209 |
-
4. **Medical stents**: Auxetic origami tubes expand radially when stretched axially, useful for vascular stents.
|
| 1210 |
-
|
| 1211 |
-
#### For Simulation
|
| 1212 |
-
|
| 1213 |
-
The effective Poisson's ratio of an origami pattern can be computed from the kinematic equations:
|
| 1214 |
-
|
| 1215 |
-
```
|
| 1216 |
-
nu_eff = -(d(epsilon_y)/d(epsilon_x))
|
| 1217 |
-
|
| 1218 |
-
where epsilon_x, epsilon_y are the effective in-plane strains
|
| 1219 |
-
computed from the fold-angle-dependent geometry.
|
| 1220 |
-
```
|
| 1221 |
-
|
| 1222 |
-
For Miura-ori with parallelogram angle phi and fold angle theta:
|
| 1223 |
-
|
| 1224 |
-
```
|
| 1225 |
-
nu_xy = -( (cos(theta) * tan(phi))^2 + sin(theta)^2 ) / (cos(theta)^2 * tan(phi)^2 + sin(theta)^2)
|
| 1226 |
-
```
|
| 1227 |
-
|
| 1228 |
-
This gives nu_xy = -1 for the standard Miura-ori (symmetric case), but can vary for asymmetric variants.
|
| 1229 |
-
|
| 1230 |
-
---
|
| 1231 |
-
|
| 1232 |
-
## 5. Key References and Sources
|
| 1233 |
-
|
| 1234 |
-
### Foundational Texts
|
| 1235 |
-
|
| 1236 |
-
- **Demaine, E.D. and O'Rourke, J.** (2007). *Geometric Folding Algorithms: Linkages, Origami, Polyhedra*. Cambridge University Press. -- The definitive monograph on computational origami.
|
| 1237 |
-
- **Lang, R.J.** (2011). *Origami Design Secrets: Mathematical Methods for an Ancient Art*. 2nd ed. A K Peters/CRC Press. -- Practical computational design methods including TreeMaker.
|
| 1238 |
-
- **Hull, T.C.** (2020). *Origametry: Mathematical Methods in Paper Folding*. Cambridge University Press. -- Modern mathematical treatment.
|
| 1239 |
-
|
| 1240 |
-
### Key Papers
|
| 1241 |
-
|
| 1242 |
-
- **Bern, M. and Hayes, B.** (1996). "The complexity of flat origami." *Proceedings of the 7th ACM-SIAM Symposium on Discrete Algorithms (SODA)*. -- Proved NP-completeness of flat foldability.
|
| 1243 |
-
- **Akitaya, H.A. et al.** (2016). "Box pleating is hard." *Proceedings of the 16th Japan Conference on Discrete and Computational Geometry and Graphs*. -- Repaired and strengthened Bern-Hayes proof.
|
| 1244 |
-
- **Hull, T.C. and Zakharevich, I.** (2025). "Flat origami is Turing complete." *arXiv:2309.07932v4*. -- Showed flat origami with optional creases can simulate universal computation.
|
| 1245 |
-
- **Schenk, M. and Guest, S.D.** (2011). "Origami folding: A structural engineering approach." *Origami 5: Fifth International Meeting of Origami Science, Mathematics, and Education*. -- Bar-and-hinge model foundations.
|
| 1246 |
-
- **Ghassaei, A. et al.** (2018). "Fast, interactive origami simulation using GPU computation." *Origami 7*. -- GPU-accelerated bar-and-hinge implementation (origamisimulator.org).
|
| 1247 |
-
- **Tachi, T.** (2009). "Simulation of rigid origami." *Origami 4: Fourth International Meeting of Origami Science, Mathematics, and Education*. -- Rigid origami simulator.
|
| 1248 |
-
- **Demaine, E.D., Demaine, M.L., and Lubiw, A.** (1998). "Folding and one straight cut suffice." *Proceedings of the 10th ACM-SIAM Symposium on Discrete Algorithms*. -- Fold-and-cut theorem.
|
| 1249 |
-
- **Liu, K. and Paulino, G.H.** (2017). "Nonlinear mechanics of non-rigid origami: an efficient computational approach." *Proceedings of the Royal Society A*, 473(2206). -- Advanced bar-and-hinge mechanics.
|
| 1250 |
-
|
| 1251 |
-
### Computational Complexity
|
| 1252 |
-
|
| 1253 |
-
- **Stern, A. and Hull, T.** (2025). "Computational Complexities of Folding." *arXiv:2410.07666*. -- Comprehensive survey of complexity results including FPT, PSPACE, #P, and undecidability results.
|
| 1254 |
-
- **Hull, T.C.** (1994). "On the mathematics of flat origamis." *Congressus Numerantium*, 100, 215-224. -- Sufficiency of Kawasaki's condition, counting MV assignments.
|
| 1255 |
-
|
| 1256 |
-
### Engineering and Mechanics
|
| 1257 |
-
|
| 1258 |
-
- **Filipov, E.T., Liu, K., Tachi, T., Schenk, M., and Paulino, G.H.** (2017). "Bar and hinge models for scalable analysis of origami." *International Journal of Solids and Structures*, 124, 26-45. -- Comprehensive bar-and-hinge formulation.
|
| 1259 |
-
- **Lang, R.J. et al.** (2018). "A review of thickness-accommodation techniques in origami-inspired engineering." *Applied Mechanics Reviews*, 70(1), 010805. -- Survey of thickness methods.
|
| 1260 |
-
- **Silverberg, J.L. et al.** (2014). "Using origami design principles to fold reprogrammable mechanical metamaterials." *Science*, 345(6197), 647-650. -- Programmable mechanical properties.
|
| 1261 |
-
- **Yasuda, H. and Yang, J.** (2015). "Reentrant origami-based metamaterials with negative Poisson's ratio and bistability." *Physical Review Letters*, 114(18), 185502. -- Auxetic and bistable origami.
|
| 1262 |
-
- **Lechenault, F., Thiria, B., and Adda-Bedia, M.** (2014). "Mechanical response of a creased sheet." *Physical Review Letters*, 112, 244301. -- Elastic theory of creases.
|
| 1263 |
-
|
| 1264 |
-
### Software Tools
|
| 1265 |
-
|
| 1266 |
-
- **Origami Simulator** (Ghassaei): https://origamisimulator.org/ -- Browser-based GPU-accelerated simulator.
|
| 1267 |
-
- **Rigid Origami Simulator** (Tachi): https://origami.c.u-tokyo.ac.jp/~tachi/software/ -- Rigid origami kinematic simulator.
|
| 1268 |
-
- **ORIPA** (Mitani): Crease pattern editor with flat-foldability checks.
|
| 1269 |
-
- **TreeMaker** (Lang): Computational origami design from stick figures.
|
| 1270 |
-
- **Rabbit Ear** (Kraft): JavaScript library for computational origami with Kawasaki/Maekawa solvers and layer ordering.
|
| 1271 |
-
|
| 1272 |
-
---
|
| 1273 |
-
|
| 1274 |
-
## Appendix A: Huzita-Hatori Axioms (Origami Constructive Power)
|
| 1275 |
-
|
| 1276 |
-
The **seven Huzita-Hatori axioms** define all possible single-fold operations in origami and establish that origami construction is strictly more powerful than compass-and-straightedge construction.
|
| 1277 |
-
|
| 1278 |
-
| Axiom | Description | Geometric Power |
|
| 1279 |
-
|-------|-------------|----------------|
|
| 1280 |
-
| O1 | Given two points, fold a line through both. | Line through 2 points (same as straightedge) |
|
| 1281 |
-
| O2 | Given two points, fold one onto the other. | Perpendicular bisector |
|
| 1282 |
-
| O3 | Given two lines, fold one onto the other. | Angle bisector |
|
| 1283 |
-
| O4 | Given a point and a line, fold a perpendicular through the point. | Perpendicular through a point |
|
| 1284 |
-
| O5 | Given two points and a line, fold one point onto the line through the other. | Solves quadratic equations |
|
| 1285 |
-
| O6 | Given two points and two lines, fold each point onto its line simultaneously. | **Solves cubic equations** |
|
| 1286 |
-
| O7 | Given a point and two lines, fold the point onto one line with the fold perpendicular to the other. | Perpendicular fold onto a line |
|
| 1287 |
-
|
| 1288 |
-
**Axiom O6 is the key**: It allows origami to solve cubic equations, which compass and straightedge cannot do. This enables:
|
| 1289 |
-
- **Angle trisection** (impossible with compass and straightedge).
|
| 1290 |
-
- **Doubling the cube** (impossible with compass and straightedge).
|
| 1291 |
-
- Construction of regular heptagon (7-sided polygon).
|
| 1292 |
-
|
| 1293 |
-
These axioms were discovered by Jacques Justin (1986), rediscovered by Humiaki Huzita (1991), with axiom O7 found by Koshiro Hatori (2001) and independently by Robert Lang.
|
| 1294 |
-
|
| 1295 |
-
---
|
| 1296 |
-
|
| 1297 |
-
## Appendix B: Summary of Key Formulas for Simulation Engine Implementation
|
| 1298 |
-
|
| 1299 |
-
### Geometric Validation
|
| 1300 |
-
|
| 1301 |
-
```
|
| 1302 |
-
Kawasaki-Justin: sum_{i odd} alpha_i = pi
|
| 1303 |
-
Maekawa-Justin: |M - V| = 2
|
| 1304 |
-
Big-Little-Big: if alpha_{i-1} > alpha_i < alpha_{i+1},
|
| 1305 |
-
then MV(crease_i) != MV(crease_{i+1})
|
| 1306 |
-
```
|
| 1307 |
-
|
| 1308 |
-
### Energy Model (Bar-and-Hinge)
|
| 1309 |
-
|
| 1310 |
-
```
|
| 1311 |
-
E_total = E_bar + E_facet + E_fold
|
| 1312 |
-
|
| 1313 |
-
E_bar = sum (1/2) * k_a * (L - L0)^2
|
| 1314 |
-
E_facet = sum (1/2) * k_f * l * (theta - pi)^2
|
| 1315 |
-
E_fold = sum (1/2) * k_c * l * (rho - rho_target)^2
|
| 1316 |
-
|
| 1317 |
-
k_a = E*t*w/L0 (axial stiffness)
|
| 1318 |
-
k_f = E*t^3/(12*(1-nu^2)) (per unit length, flexural)
|
| 1319 |
-
k_c = crease torsional stiffness (per unit length)
|
| 1320 |
-
```
|
| 1321 |
-
|
| 1322 |
-
### Rigid Origami Kinematics (Degree-4 Vertex)
|
| 1323 |
-
|
| 1324 |
-
```
|
| 1325 |
-
tan(rho_2/2) = -cos((alpha-beta)/2) / cos((alpha+beta)/2) * tan(rho_1/2)
|
| 1326 |
-
```
|
| 1327 |
-
|
| 1328 |
-
### Origami Length Scale
|
| 1329 |
-
|
| 1330 |
-
```
|
| 1331 |
-
L* = B / kappa = (E*t^3/12) / kappa
|
| 1332 |
-
|
| 1333 |
-
L* >> panel_size --> rigid origami regime
|
| 1334 |
-
L* << panel_size --> flexible shell regime
|
| 1335 |
-
```
|
| 1336 |
-
|
| 1337 |
-
### Deployment Ratio
|
| 1338 |
-
|
| 1339 |
-
```
|
| 1340 |
-
DR = L_deployed / L_stowed
|
| 1341 |
-
DR_area = A_deployed / A_stowed
|
| 1342 |
-
```
|
| 1343 |
-
|
| 1344 |
-
### Effective Poisson's Ratio (Miura-ori)
|
| 1345 |
-
|
| 1346 |
-
```
|
| 1347 |
-
nu_xy = -((cos(theta)*tan(phi))^2 + sin(theta)^2) /
|
| 1348 |
-
(cos(theta)^2*tan(phi)^2 + sin(theta)^2)
|
| 1349 |
-
```
|
| 1350 |
-
|
| 1351 |
-
### Fatigue Damage
|
| 1352 |
-
|
| 1353 |
-
```
|
| 1354 |
-
D(N) = (N / N_f)^p
|
| 1355 |
-
kappa_N = kappa_0 * (1 - D(N))
|
| 1356 |
-
```
|
| 1357 |
-
|
| 1358 |
-
### Numerical Integration (Verlet, for dynamic simulation)
|
| 1359 |
-
|
| 1360 |
-
```
|
| 1361 |
-
x_{n+1} = 2*x_n - x_{n-1} + F * dt^2 / m
|
| 1362 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/metrics.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
| 1 |
-
# Metrics for the Origami RL Environment
|
| 2 |
-
|
| 3 |
-
## Fold Validity
|
| 4 |
-
|
| 5 |
-
| Metric | Formula | Use |
|
| 6 |
-
|--------|---------|-----|
|
| 7 |
-
| Kawasaki violation | Σ |alternating_angle_sum - 180°| per vertex | Reward penalty |
|
| 8 |
-
| Maekawa violation | Σ |abs(M-V) - 2| per vertex | Hard constraint |
|
| 9 |
-
| Self-intersection count | Triangle-triangle intersection testing | Reward penalty |
|
| 10 |
-
| Rigid foldability | Kinematic simulation pass/fail | Gate reward |
|
| 11 |
-
|
| 12 |
-
## Compactness / Efficiency
|
| 13 |
-
|
| 14 |
-
| Metric | Formula | Use |
|
| 15 |
-
|--------|---------|-----|
|
| 16 |
-
| **Deployment ratio** | `area_folded / area_unfolded` | Primary reward signal |
|
| 17 |
-
| Volume compaction | `bbox_folded / bbox_unfolded` | Secondary reward |
|
| 18 |
-
| Fold count | Count of M + V edges | Efficiency penalty |
|
| 19 |
-
| Folding efficiency | `compaction_ratio / fold_count` | Combined metric |
|
| 20 |
-
| Packing efficiency | `material_volume / bbox_volume` | How well it fills space |
|
| 21 |
-
|
| 22 |
-
## Structural / Stress
|
| 23 |
-
|
| 24 |
-
| Metric | Formula | Use |
|
| 25 |
-
|--------|---------|-----|
|
| 26 |
-
| Cauchy strain | Per-vertex avg deviation of edge lengths | Reward penalty |
|
| 27 |
-
| Max strain | `max(strain_per_vertex)` | Must stay below material limit |
|
| 28 |
-
| Structural energy | Sum of constraint violation energies | Lower = more stable |
|
| 29 |
-
|
| 30 |
-
## Shape Similarity (for target-matching tasks)
|
| 31 |
-
|
| 32 |
-
| Metric | Formula | Use |
|
| 33 |
-
|--------|---------|-----|
|
| 34 |
-
| Chamfer distance | Avg nearest-point distance between shapes | Primary shape reward |
|
| 35 |
-
| Hausdorff distance | Max distance between shapes | Worst-case shape error |
|
| 36 |
-
| IoU (3D) | Voxelized intersection over union | Alternative shape reward |
|
| 37 |
-
|
| 38 |
-
## Foldability Quality
|
| 39 |
-
|
| 40 |
-
| Metric | Formula | Use |
|
| 41 |
-
|--------|---------|-----|
|
| 42 |
-
| Flat-foldability score | Sum of angular deviations from target | For flat-fold tasks |
|
| 43 |
-
| Deployability | Reverse fold simulation collision check | Engineering reward |
|
| 44 |
-
| Crease pattern complexity | Entropy of M/V assignments | Simplicity bonus |
|
| 45 |
-
|
| 46 |
-
## Composite Reward Function
|
| 47 |
-
|
| 48 |
-
```python
|
| 49 |
-
reward = (
|
| 50 |
-
w1 * deployment_ratio # How compact is the fold?
|
| 51 |
-
- w2 * total_strain # Physical validity
|
| 52 |
-
- w3 * self_intersections # No paper penetration
|
| 53 |
-
- w4 * kawasaki_violation # Angle consistency
|
| 54 |
-
- w5 * fold_count_penalty # Fewer folds = better
|
| 55 |
-
+ w6 * deployability_score # Can it unfold cleanly?
|
| 56 |
-
+ w7 * shape_similarity # If targeting a specific shape
|
| 57 |
-
)
|
| 58 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/origami_simulator_code.md
DELETED
|
@@ -1,1030 +0,0 @@
|
|
| 1 |
-
# OrigamiSimulator Source Code Analysis & FOLD Format in Practice
|
| 2 |
-
|
| 3 |
-
> Deep analysis of Amanda Ghassaei's OrigamiSimulator codebase and real FOLD file examples.
|
| 4 |
-
> Source: https://github.com/amandaghassaei/OrigamiSimulator (MIT License, JS/WebGL)
|
| 5 |
-
> FOLD spec: https://github.com/edemaine/fold
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## Table of Contents
|
| 10 |
-
|
| 11 |
-
1. [Repository Structure](#1-repository-structure)
|
| 12 |
-
2. [FOLD Format Parsing — How the Simulator Loads Files](#2-fold-format-parsing)
|
| 13 |
-
3. [Mesh & Geometry Representation](#3-mesh--geometry-representation)
|
| 14 |
-
4. [Triangulation — How Faces Are Split](#4-triangulation)
|
| 15 |
-
5. [The Simulation Model — GPU-Accelerated Bar-and-Hinge](#5-the-simulation-model)
|
| 16 |
-
6. [Strain Computation & Visualization](#6-strain-computation--visualization)
|
| 17 |
-
7. [Real FOLD File Examples](#7-real-fold-file-examples)
|
| 18 |
-
8. [Minimal FOLD Representation for Our RL Environment](#8-minimal-fold-representation)
|
| 19 |
-
|
| 20 |
-
---
|
| 21 |
-
|
| 22 |
-
## 1. Repository Structure
|
| 23 |
-
|
| 24 |
-
The OrigamiSimulator repo (the browser-based version at origamisimulator.org) has this structure:
|
| 25 |
-
|
| 26 |
-
```
|
| 27 |
-
OrigamiSimulator/
|
| 28 |
-
├── index.html
|
| 29 |
-
├── js/
|
| 30 |
-
│ ├── globals.js # Global constants, stiffness params, simulation settings
|
| 31 |
-
│ ├── model.js # Main model class — orchestrates everything
|
| 32 |
-
│ ├── fold.js # FOLD format import/export
|
| 33 |
-
│ ├── pattern.js # Built-in crease patterns (bird base, Miura-ori, etc.)
|
| 34 |
-
│ ├── SVGimport.js # SVG import (converts SVG crease patterns to FOLD)
|
| 35 |
-
│ ├── triangulate.js # Ear-clipping triangulation of polygon faces
|
| 36 |
-
│ ├── gpuMath.js # WebGL compute abstraction (textures as data arrays)
|
| 37 |
-
│ ├── solver.js # The GPU constraint solver (velocity Verlet integration)
|
| 38 |
-
│ ├── node.js # Vertex/node class
|
| 39 |
-
│ ├── edge.js # Edge class (with assignment: M/V/B/F/U)
|
| 40 |
-
│ ├── face.js # Face/triangle class
|
| 41 |
-
│ ├── beam.js # Beam (bar) constraint — axial spring
|
| 42 |
-
│ ├── crease.js # Crease constraint — fold/facet hinge
|
| 43 |
-
│ ├── threeView.js # Three.js 3D rendering
|
| 44 |
-
│ └── UI/ # User interface code
|
| 45 |
-
├── assets/
|
| 46 |
-
│ ├── fold/ # Example .fold files (crane, bird, Miura-ori, etc.)
|
| 47 |
-
│ └── svg/ # Example SVG crease patterns
|
| 48 |
-
└── shaders/
|
| 49 |
-
├── positionCalcShader.frag # Position update (Verlet integration)
|
| 50 |
-
├── velocityCalcShader.frag # Velocity calculation with damping
|
| 51 |
-
├── thetaCalcShader.frag # Dihedral angle computation
|
| 52 |
-
├── normalCalcShader.frag # Face normal computation
|
| 53 |
-
└── strainCalcShader.frag # Strain visualization
|
| 54 |
-
```
|
| 55 |
-
|
| 56 |
-
---
|
| 57 |
-
|
| 58 |
-
## 2. FOLD Format Parsing — How the Simulator Loads Files
|
| 59 |
-
|
| 60 |
-
### The Core Import Logic (`fold.js`)
|
| 61 |
-
|
| 62 |
-
The simulator's FOLD import is in `js/fold.js`. Here is the essential parsing logic:
|
| 63 |
-
|
| 64 |
-
```javascript
|
| 65 |
-
// fold.js — FOLD import (reconstructed from source)
|
| 66 |
-
|
| 67 |
-
function parseFOLD(foldData) {
|
| 68 |
-
// foldData is the parsed JSON object from a .fold file
|
| 69 |
-
|
| 70 |
-
var vertices = foldData.vertices_coords; // [[x,y], [x,y,z], ...]
|
| 71 |
-
var edges = foldData.edges_vertices; // [[v0,v1], [v0,v1], ...]
|
| 72 |
-
var assignments = foldData.edges_assignment; // ["M","V","B","F","U",...]
|
| 73 |
-
var foldAngles = foldData.edges_foldAngle; // [angle, angle, ...] (degrees)
|
| 74 |
-
var faces = foldData.faces_vertices; // [[v0,v1,v2,...], ...]
|
| 75 |
-
|
| 76 |
-
// If vertices are 2D, add z=0
|
| 77 |
-
for (var i = 0; i < vertices.length; i++) {
|
| 78 |
-
if (vertices[i].length === 2) {
|
| 79 |
-
vertices[i].push(0);
|
| 80 |
-
}
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
// If edges_assignment is missing, infer from edges_foldAngle
|
| 84 |
-
if (!assignments && foldAngles) {
|
| 85 |
-
assignments = [];
|
| 86 |
-
for (var i = 0; i < foldAngles.length; i++) {
|
| 87 |
-
if (foldAngles[i] === 0) assignments.push("F");
|
| 88 |
-
else if (foldAngles[i] < 0) assignments.push("M");
|
| 89 |
-
else if (foldAngles[i] > 0) assignments.push("V");
|
| 90 |
-
else assignments.push("U");
|
| 91 |
-
}
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
// If edges_foldAngle is missing, infer from edges_assignment
|
| 95 |
-
if (!foldAngles && assignments) {
|
| 96 |
-
foldAngles = [];
|
| 97 |
-
for (var i = 0; i < assignments.length; i++) {
|
| 98 |
-
if (assignments[i] === "M") foldAngles.push(-Math.PI);
|
| 99 |
-
else if (assignments[i] === "V") foldAngles.push(Math.PI);
|
| 100 |
-
else foldAngles.push(0);
|
| 101 |
-
}
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
// If faces_vertices is missing, reconstruct from edges
|
| 105 |
-
if (!faces) {
|
| 106 |
-
faces = FOLD.convert.edges_vertices_to_faces_vertices(
|
| 107 |
-
vertices, edges
|
| 108 |
-
);
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
return {
|
| 112 |
-
vertices: vertices,
|
| 113 |
-
edges: edges,
|
| 114 |
-
assignments: assignments,
|
| 115 |
-
foldAngles: foldAngles,
|
| 116 |
-
faces: faces
|
| 117 |
-
};
|
| 118 |
-
}
|
| 119 |
-
```
|
| 120 |
-
|
| 121 |
-
### Key FOLD Fields Actually Used by the Simulator
|
| 122 |
-
|
| 123 |
-
| FOLD Field | Required? | How It's Used |
|
| 124 |
-
|-----------|-----------|---------------|
|
| 125 |
-
| `vertices_coords` | **YES** | Node positions (2D or 3D). 2D gets z=0 appended. |
|
| 126 |
-
| `edges_vertices` | **YES** | Defines connectivity. Each edge is a pair `[v_i, v_j]`. |
|
| 127 |
-
| `edges_assignment` | Recommended | `"M"`, `"V"`, `"B"`, `"F"`, `"U"` — determines fold behavior. |
|
| 128 |
-
| `edges_foldAngle` | Optional | Target fold angle in radians (some files use degrees). The simulator converts. Positive = valley, negative = mountain. |
|
| 129 |
-
| `faces_vertices` | Recommended | Polygon faces as ordered vertex lists. If missing, reconstructed from edges. |
|
| 130 |
-
| `file_spec` | Ignored | FOLD spec version |
|
| 131 |
-
| `file_creator` | Ignored | Metadata |
|
| 132 |
-
| `frame_classes` | Checked | `"creasePattern"` vs `"foldedForm"` — affects initial state |
|
| 133 |
-
| `frame_attributes` | Checked | `"2D"` vs `"3D"` |
|
| 134 |
-
| `faceOrders` | **NOT USED** | Layer ordering is not needed for physics simulation |
|
| 135 |
-
| `vertices_vertices` | **NOT USED** | Adjacency — recomputed internally |
|
| 136 |
-
| `edges_faces` | **NOT USED** | Recomputed internally |
|
| 137 |
-
| `faces_edges` | **NOT USED** | Recomputed internally |
|
| 138 |
-
|
| 139 |
-
### Critical Insight: What the Simulator Does NOT Use
|
| 140 |
-
|
| 141 |
-
The simulator ignores `faceOrders` (layer ordering) entirely. It relies on physics simulation (constraint solving) rather than combinatorial layer ordering. Self-intersection is handled implicitly by the energy-based solver — faces naturally avoid each other if the stiffness parameters are set correctly.
|
| 142 |
-
|
| 143 |
-
### Assignment-to-Angle Mapping
|
| 144 |
-
|
| 145 |
-
```javascript
|
| 146 |
-
// How assignments map to target fold angles:
|
| 147 |
-
// "M" (mountain): target angle = -PI radians (fold to -180 degrees)
|
| 148 |
-
// "V" (valley): target angle = +PI radians (fold to +180 degrees)
|
| 149 |
-
// "F" (flat): target angle = 0 (no fold)
|
| 150 |
-
// "B" (boundary): no fold constraint (boundary edge)
|
| 151 |
-
// "U" (unassigned): target angle = 0 (treated as flat)
|
| 152 |
-
|
| 153 |
-
// The fold angle convention:
|
| 154 |
-
// 0 = flat (faces coplanar)
|
| 155 |
-
// +PI = valley fold (paper folds toward you)
|
| 156 |
-
// -PI = mountain fold (paper folds away from you)
|
| 157 |
-
// The actual simulation interpolates: target = foldAngle * foldPercent
|
| 158 |
-
// where foldPercent goes from 0.0 (flat) to 1.0 (fully folded)
|
| 159 |
-
```
|
| 160 |
-
|
| 161 |
-
---
|
| 162 |
-
|
| 163 |
-
## 3. Mesh & Geometry Representation
|
| 164 |
-
|
| 165 |
-
### Internal Data Structures
|
| 166 |
-
|
| 167 |
-
The simulator converts the FOLD data into internal arrays optimized for GPU computation:
|
| 168 |
-
|
| 169 |
-
```javascript
|
| 170 |
-
// model.js — Internal representation (reconstructed)
|
| 171 |
-
|
| 172 |
-
// NODES: stored as flat Float32Arrays for GPU textures
|
| 173 |
-
// Position texture: [x0, y0, z0, w0, x1, y1, z1, w1, ...]
|
| 174 |
-
// where w is unused (padding for RGBA texture format)
|
| 175 |
-
var numNodes;
|
| 176 |
-
var originalPosition; // Float32Array — rest positions (flat state)
|
| 177 |
-
var position; // Float32Array — current positions (deformed state)
|
| 178 |
-
var velocity; // Float32Array — current velocities
|
| 179 |
-
var lastPosition; // Float32Array — previous positions (for Verlet)
|
| 180 |
-
var externalForces; // Float32Array — applied external forces
|
| 181 |
-
var mass; // Float32Array — per-node mass (usually uniform)
|
| 182 |
-
|
| 183 |
-
// BEAMS (bars): axial spring constraints along every edge
|
| 184 |
-
var numBeams;
|
| 185 |
-
var beamMeta; // Int32Array — [nodeA_index, nodeB_index] per beam
|
| 186 |
-
var beamK; // Float32Array — axial stiffness per beam
|
| 187 |
-
|
| 188 |
-
// CREASES: rotational spring constraints (both fold and facet hinges)
|
| 189 |
-
var numCreases;
|
| 190 |
-
var creaseMeta; // Int32Array — [node1, node2, node3, node4] per crease
|
| 191 |
-
// node1-node2 is the hinge edge
|
| 192 |
-
// node3, node4 are the opposite vertices of the two triangles
|
| 193 |
-
var creaseAngles; // Float32Array — target dihedral angle per crease
|
| 194 |
-
var creaseStiffness; // Float32Array — torsional stiffness per crease
|
| 195 |
-
var creaseType; // Int32Array — 0=fold crease, 1=facet crease, 2=boundary
|
| 196 |
-
|
| 197 |
-
// The four-node crease geometry:
|
| 198 |
-
// node3
|
| 199 |
-
// / | \
|
| 200 |
-
// / | \
|
| 201 |
-
// node1-+--node2 (hinge edge)
|
| 202 |
-
// \ | /
|
| 203 |
-
// \ | /
|
| 204 |
-
// node4
|
| 205 |
-
```
|
| 206 |
-
|
| 207 |
-
### How Vertices, Edges, Faces Map to GPU Textures
|
| 208 |
-
|
| 209 |
-
The simulator packs all data into WebGL textures (RGBA float textures) because WebGL fragment shaders operate on textures:
|
| 210 |
-
|
| 211 |
-
```javascript
|
| 212 |
-
// gpuMath.js — texture packing (conceptual)
|
| 213 |
-
|
| 214 |
-
// Each vertex gets one pixel in a position texture:
|
| 215 |
-
// pixel[i] = vec4(x_i, y_i, z_i, 0.0)
|
| 216 |
-
//
|
| 217 |
-
// Texture dimensions: ceil(sqrt(numNodes)) x ceil(sqrt(numNodes))
|
| 218 |
-
// So 100 nodes -> 10x10 texture
|
| 219 |
-
//
|
| 220 |
-
// Beams packed into beam meta texture:
|
| 221 |
-
// pixel[i] = vec4(nodeA_index, nodeB_index, restLength, stiffness)
|
| 222 |
-
//
|
| 223 |
-
// Creases packed into crease meta texture:
|
| 224 |
-
// pixel[i] = vec4(node1_index, node2_index, node3_index, node4_index)
|
| 225 |
-
// (target angle and stiffness in a separate texture)
|
| 226 |
-
|
| 227 |
-
function initTextureFromArray(width, height, data, type) {
|
| 228 |
-
var gl = this.gl;
|
| 229 |
-
var texture = gl.createTexture();
|
| 230 |
-
gl.bindTexture(gl.TEXTURE_2D, texture);
|
| 231 |
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,
|
| 232 |
-
width, height, 0, gl.RGBA, type, data);
|
| 233 |
-
// NEAREST filtering — no interpolation (we want exact values)
|
| 234 |
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
| 235 |
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
| 236 |
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
| 237 |
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
| 238 |
-
return texture;
|
| 239 |
-
}
|
| 240 |
-
```
|
| 241 |
-
|
| 242 |
-
---
|
| 243 |
-
|
| 244 |
-
## 4. Triangulation — How Faces Are Split
|
| 245 |
-
|
| 246 |
-
### Why Triangulate?
|
| 247 |
-
|
| 248 |
-
FOLD files can have polygon faces (quads, pentagons, etc.). The bar-and-hinge model requires triangulated faces because:
|
| 249 |
-
1. Triangles are always planar (3 points define a plane).
|
| 250 |
-
2. Non-triangular faces need "facet hinges" to penalize bending — but you need triangles to define a dihedral angle.
|
| 251 |
-
3. GPU rendering works with triangles.
|
| 252 |
-
|
| 253 |
-
### The Triangulation Algorithm
|
| 254 |
-
|
| 255 |
-
The simulator uses **ear-clipping triangulation** for each polygon face:
|
| 256 |
-
|
| 257 |
-
```javascript
|
| 258 |
-
// triangulate.js (reconstructed from source)
|
| 259 |
-
|
| 260 |
-
function triangulateFace(face, vertices) {
|
| 261 |
-
// face = [v0, v1, v2, v3, ...] (vertex indices, CCW order)
|
| 262 |
-
// vertices = [[x,y,z], ...] (all vertex positions)
|
| 263 |
-
|
| 264 |
-
if (face.length === 3) return [face]; // already a triangle
|
| 265 |
-
|
| 266 |
-
var triangles = [];
|
| 267 |
-
var remaining = face.slice(); // copy
|
| 268 |
-
|
| 269 |
-
while (remaining.length > 3) {
|
| 270 |
-
// Find an "ear" — a vertex whose triangle doesn't contain other vertices
|
| 271 |
-
for (var i = 0; i < remaining.length; i++) {
|
| 272 |
-
var prev = remaining[(i - 1 + remaining.length) % remaining.length];
|
| 273 |
-
var curr = remaining[i];
|
| 274 |
-
var next = remaining[(i + 1) % remaining.length];
|
| 275 |
-
|
| 276 |
-
// Check if triangle (prev, curr, next) is an ear
|
| 277 |
-
if (isEar(prev, curr, next, remaining, vertices)) {
|
| 278 |
-
triangles.push([prev, curr, next]);
|
| 279 |
-
remaining.splice(i, 1); // remove the ear vertex
|
| 280 |
-
break;
|
| 281 |
-
}
|
| 282 |
-
}
|
| 283 |
-
}
|
| 284 |
-
triangles.push(remaining); // last 3 vertices form final triangle
|
| 285 |
-
return triangles;
|
| 286 |
-
}
|
| 287 |
-
|
| 288 |
-
function isEar(a, b, c, polygon, vertices) {
|
| 289 |
-
// 1. Triangle must be convex (CCW winding)
|
| 290 |
-
var cross = crossProduct2D(
|
| 291 |
-
sub(vertices[b], vertices[a]),
|
| 292 |
-
sub(vertices[c], vertices[a])
|
| 293 |
-
);
|
| 294 |
-
if (cross <= 0) return false; // concave, not an ear
|
| 295 |
-
|
| 296 |
-
// 2. No other polygon vertex inside the triangle
|
| 297 |
-
for (var i = 0; i < polygon.length; i++) {
|
| 298 |
-
var v = polygon[i];
|
| 299 |
-
if (v === a || v === b || v === c) continue;
|
| 300 |
-
if (pointInTriangle(vertices[v], vertices[a], vertices[b], vertices[c])) {
|
| 301 |
-
return false;
|
| 302 |
-
}
|
| 303 |
-
}
|
| 304 |
-
return true;
|
| 305 |
-
}
|
| 306 |
-
```
|
| 307 |
-
|
| 308 |
-
### What Triangulation Creates
|
| 309 |
-
|
| 310 |
-
For a quad face `[v0, v1, v2, v3]`, triangulation produces two triangles `[v0, v1, v2]` and `[v0, v2, v3]`. The diagonal edge `(v0, v2)` is a **new internal edge** that becomes a **facet hinge** (not a fold crease). This facet hinge has:
|
| 311 |
-
- Target angle = PI (flat, 180 degrees)
|
| 312 |
-
- High stiffness (penalizes face bending)
|
| 313 |
-
|
| 314 |
-
```
|
| 315 |
-
Original quad face: After triangulation:
|
| 316 |
-
v0 ------- v1 v0 ------- v1
|
| 317 |
-
| | | \ |
|
| 318 |
-
| | ---> | \ |
|
| 319 |
-
| | | \ |
|
| 320 |
-
v3 ------- v2 v3 ------\ v2
|
| 321 |
-
|
| 322 |
-
The diagonal v0-v2 becomes a facet hinge.
|
| 323 |
-
```
|
| 324 |
-
|
| 325 |
-
### Edge Classification After Triangulation
|
| 326 |
-
|
| 327 |
-
Every edge in the triangulated mesh is one of three types:
|
| 328 |
-
|
| 329 |
-
| Edge Type | Source | Target Angle | Stiffness | Purpose |
|
| 330 |
-
|-----------|--------|--------------|-----------|---------|
|
| 331 |
-
| **Fold crease** | Original crease edge (M/V) | From `edges_foldAngle` | `k_fold` (user-adjustable) | Drives folding |
|
| 332 |
-
| **Facet hinge** | Triangulation diagonal OR original flat edge | PI (flat) | `k_facet` (high) | Prevents face bending |
|
| 333 |
-
| **Boundary** | Original boundary edge (B) | None | N/A | No rotational constraint |
|
| 334 |
-
|
| 335 |
-
---
|
| 336 |
-
|
| 337 |
-
## 5. The Simulation Model — GPU-Accelerated Bar-and-Hinge
|
| 338 |
-
|
| 339 |
-
### Overview of the Algorithm
|
| 340 |
-
|
| 341 |
-
The simulator uses **velocity Verlet integration** with three types of constraints, all computed in GPU fragment shaders:
|
| 342 |
-
|
| 343 |
-
```
|
| 344 |
-
For each simulation step:
|
| 345 |
-
1. Compute all forces on each node
|
| 346 |
-
a. Beam forces (axial springs — prevent stretching)
|
| 347 |
-
b. Crease forces (rotational springs — drive folding / prevent face bending)
|
| 348 |
-
2. Update velocities (with damping)
|
| 349 |
-
3. Update positions (Verlet integration)
|
| 350 |
-
4. Repeat until convergence or user stops
|
| 351 |
-
```
|
| 352 |
-
|
| 353 |
-
### The Three Constraint Types
|
| 354 |
-
|
| 355 |
-
#### Constraint 1: Beam (Bar) — Axial Spring
|
| 356 |
-
|
| 357 |
-
Each edge of the triangulated mesh is a bar that resists stretching/compression:
|
| 358 |
-
|
| 359 |
-
```glsl
|
| 360 |
-
// Conceptual beam force computation (from solver shaders)
|
| 361 |
-
// For a beam between nodes A and B:
|
| 362 |
-
|
| 363 |
-
vec3 posA = getPosition(nodeA_index); // current position of node A
|
| 364 |
-
vec3 posB = getPosition(nodeB_index); // current position of node B
|
| 365 |
-
|
| 366 |
-
vec3 delta = posB - posA;
|
| 367 |
-
float currentLength = length(delta);
|
| 368 |
-
float restLength = getRestLength(beam_index);
|
| 369 |
-
|
| 370 |
-
// Engineering strain
|
| 371 |
-
float strain = (currentLength - restLength) / restLength;
|
| 372 |
-
|
| 373 |
-
// Hooke's law: F = k * strain * restLength (force magnitude)
|
| 374 |
-
float forceMagnitude = beamStiffness * strain * restLength;
|
| 375 |
-
|
| 376 |
-
// Force direction: along the beam
|
| 377 |
-
vec3 forceDirection = delta / currentLength;
|
| 378 |
-
|
| 379 |
-
// Force on node A: pulls toward B if stretched, pushes away if compressed
|
| 380 |
-
vec3 forceOnA = forceMagnitude * forceDirection;
|
| 381 |
-
// Force on node B: equal and opposite
|
| 382 |
-
vec3 forceOnB = -forceMagnitude * forceDirection;
|
| 383 |
-
```
|
| 384 |
-
|
| 385 |
-
The axial stiffness parameter:
|
| 386 |
-
```javascript
|
| 387 |
-
// globals.js
|
| 388 |
-
var axialStiffness = 70; // default value — high to prevent stretching
|
| 389 |
-
// This maps to: k_beam = axialStiffness * E * t / L0
|
| 390 |
-
// where E = Young's modulus, t = thickness, L0 = rest length
|
| 391 |
-
```
|
| 392 |
-
|
| 393 |
-
#### Constraint 2: Crease — Rotational Spring (Fold Hinge)
|
| 394 |
-
|
| 395 |
-
For each crease (fold line), a rotational spring drives the dihedral angle toward the target:
|
| 396 |
-
|
| 397 |
-
```glsl
|
| 398 |
-
// Conceptual crease force computation
|
| 399 |
-
// A crease spans 4 nodes: node1, node2 (hinge edge), node3, node4 (wing tips)
|
| 400 |
-
//
|
| 401 |
-
// node3
|
| 402 |
-
// / | \
|
| 403 |
-
// / | \ dihedral angle theta is measured between
|
| 404 |
-
// node1----node2 the planes (node1,node2,node3) and (node1,node2,node4)
|
| 405 |
-
// \ | /
|
| 406 |
-
// \ | /
|
| 407 |
-
// node4
|
| 408 |
-
|
| 409 |
-
vec3 p1 = getPosition(node1);
|
| 410 |
-
vec3 p2 = getPosition(node2);
|
| 411 |
-
vec3 p3 = getPosition(node3);
|
| 412 |
-
vec3 p4 = getPosition(node4);
|
| 413 |
-
|
| 414 |
-
// Compute face normals
|
| 415 |
-
vec3 e = p2 - p1; // hinge edge vector
|
| 416 |
-
vec3 n1 = cross(p3 - p1, e); // normal of triangle (p1, p2, p3)
|
| 417 |
-
vec3 n2 = cross(e, p4 - p1); // normal of triangle (p1, p2, p4)
|
| 418 |
-
n1 = normalize(n1);
|
| 419 |
-
n2 = normalize(n2);
|
| 420 |
-
|
| 421 |
-
// Current dihedral angle
|
| 422 |
-
float cosTheta = dot(n1, n2);
|
| 423 |
-
float sinTheta = dot(cross(n1, n2), normalize(e));
|
| 424 |
-
float theta = atan(sinTheta, cosTheta); // current dihedral angle
|
| 425 |
-
|
| 426 |
-
// Target angle (interpolated by fold percent)
|
| 427 |
-
float targetAngle = getTargetAngle(crease_index) * foldPercent;
|
| 428 |
-
|
| 429 |
-
// Angular deviation
|
| 430 |
-
float deltaTheta = theta - targetAngle;
|
| 431 |
-
|
| 432 |
-
// Torque magnitude: tau = k_crease * edgeLength * deltaTheta
|
| 433 |
-
float torque = creaseStiffness * edgeLength * deltaTheta;
|
| 434 |
-
|
| 435 |
-
// Convert torque to forces on the 4 nodes
|
| 436 |
-
// The force on node3 is perpendicular to the hinge and the arm (p3 - hinge)
|
| 437 |
-
// The force on node4 is perpendicular to the hinge and the arm (p4 - hinge)
|
| 438 |
-
// Forces on node1 and node2 balance the torque
|
| 439 |
-
|
| 440 |
-
vec3 arm3 = p3 - project_onto_hinge(p3, p1, p2);
|
| 441 |
-
vec3 arm4 = p4 - project_onto_hinge(p4, p1, p2);
|
| 442 |
-
|
| 443 |
-
float dist3 = length(arm3);
|
| 444 |
-
float dist4 = length(arm4);
|
| 445 |
-
|
| 446 |
-
// Force on wing nodes (perpendicular to arm, in the fold direction)
|
| 447 |
-
vec3 force3 = torque / dist3 * cross(normalize(e), normalize(arm3));
|
| 448 |
-
vec3 force4 = -torque / dist4 * cross(normalize(e), normalize(arm4));
|
| 449 |
-
|
| 450 |
-
// Forces on hinge nodes balance: -(force3 + force4) split proportionally
|
| 451 |
-
```
|
| 452 |
-
|
| 453 |
-
#### Constraint 3: Facet Hinge — Keeps Faces Flat
|
| 454 |
-
|
| 455 |
-
Facet hinges are identical in implementation to fold creases, but with:
|
| 456 |
-
- **Target angle = PI** (flat / 180 degrees)
|
| 457 |
-
- **Much higher stiffness** than fold creases
|
| 458 |
-
|
| 459 |
-
```javascript
|
| 460 |
-
// Typical stiffness hierarchy:
|
| 461 |
-
var foldStiffness = 0.7; // fold creases — relatively soft, drives folding
|
| 462 |
-
var facetStiffness = 0.2; // facet hinges — moderate, keeps faces flat
|
| 463 |
-
var axialStiffness = 70; // bars — very stiff, prevents stretching
|
| 464 |
-
|
| 465 |
-
// The facet stiffness is lower than you might expect because the
|
| 466 |
-
// bar constraints already handle most of the face rigidity.
|
| 467 |
-
// The facet hinge just needs to prevent out-of-plane bending.
|
| 468 |
-
```
|
| 469 |
-
|
| 470 |
-
### The GPU Solver (Verlet Integration)
|
| 471 |
-
|
| 472 |
-
The position update shader implements velocity Verlet integration:
|
| 473 |
-
|
| 474 |
-
```glsl
|
| 475 |
-
// positionCalcShader.frag (reconstructed)
|
| 476 |
-
precision highp float;
|
| 477 |
-
|
| 478 |
-
uniform sampler2D u_position; // current positions
|
| 479 |
-
uniform sampler2D u_lastPosition; // previous positions
|
| 480 |
-
uniform sampler2D u_velocity; // current velocities
|
| 481 |
-
uniform sampler2D u_force; // total force on each node
|
| 482 |
-
uniform float u_dt; // timestep
|
| 483 |
-
uniform float u_damping; // damping coefficient [0, 1]
|
| 484 |
-
|
| 485 |
-
void main() {
|
| 486 |
-
vec2 fragCoord = gl_FragCoord.xy / u_textureDim;
|
| 487 |
-
|
| 488 |
-
vec4 pos = texture2D(u_position, fragCoord);
|
| 489 |
-
vec4 lastPos = texture2D(u_lastPosition, fragCoord);
|
| 490 |
-
vec4 force = texture2D(u_force, fragCoord);
|
| 491 |
-
|
| 492 |
-
// Velocity Verlet integration:
|
| 493 |
-
// new_pos = 2 * pos - lastPos + force * dt^2 / mass
|
| 494 |
-
// With damping: new_pos = pos + (1 - damping) * (pos - lastPos) + force * dt^2
|
| 495 |
-
|
| 496 |
-
vec4 newPos = pos + (1.0 - u_damping) * (pos - lastPos)
|
| 497 |
-
+ force * u_dt * u_dt;
|
| 498 |
-
|
| 499 |
-
gl_FragColor = newPos;
|
| 500 |
-
}
|
| 501 |
-
```
|
| 502 |
-
|
| 503 |
-
```glsl
|
| 504 |
-
// velocityCalcShader.frag (reconstructed)
|
| 505 |
-
// Velocity is derived from position difference (for damping/output)
|
| 506 |
-
|
| 507 |
-
void main() {
|
| 508 |
-
vec2 fragCoord = gl_FragCoord.xy / u_textureDim;
|
| 509 |
-
vec4 pos = texture2D(u_position, fragCoord);
|
| 510 |
-
vec4 lastPos = texture2D(u_lastPosition, fragCoord);
|
| 511 |
-
|
| 512 |
-
vec4 velocity = (pos - lastPos) / u_dt;
|
| 513 |
-
|
| 514 |
-
gl_FragColor = velocity;
|
| 515 |
-
}
|
| 516 |
-
```
|
| 517 |
-
|
| 518 |
-
### Solver Parameters
|
| 519 |
-
|
| 520 |
-
```javascript
|
| 521 |
-
// globals.js — simulation parameters
|
| 522 |
-
var numStepsPerFrame = 100; // solver iterations per render frame
|
| 523 |
-
var dt = 0.02; // timestep
|
| 524 |
-
var damping = 0.1; // velocity damping [0=no damping, 1=fully damped]
|
| 525 |
-
|
| 526 |
-
// Stiffness parameters (user-adjustable via UI sliders)
|
| 527 |
-
var axialStiffness = 70; // bar stiffness — prevents stretching
|
| 528 |
-
var foldStiffness = 0.7; // fold crease stiffness — drives folding
|
| 529 |
-
var facetStiffness = 0.2; // facet hinge stiffness — prevents bending
|
| 530 |
-
var foldPercent = 0.0; // fold amount [0=flat, 1=fully folded]
|
| 531 |
-
|
| 532 |
-
// The solver runs until:
|
| 533 |
-
// 1. Kinetic energy drops below a threshold (converged), or
|
| 534 |
-
// 2. The user changes a parameter (re-triggers), or
|
| 535 |
-
// 3. Max iterations reached
|
| 536 |
-
```
|
| 537 |
-
|
| 538 |
-
### Complete Solver Loop (Per Frame)
|
| 539 |
-
|
| 540 |
-
```javascript
|
| 541 |
-
// solver.js — main loop (reconstructed)
|
| 542 |
-
|
| 543 |
-
function solveStep() {
|
| 544 |
-
for (var i = 0; i < numStepsPerFrame; i++) {
|
| 545 |
-
// Step 1: Zero out force accumulators
|
| 546 |
-
gpuMath.clearTexture("u_force");
|
| 547 |
-
|
| 548 |
-
// Step 2: Compute beam forces
|
| 549 |
-
// For each beam, compute axial spring force
|
| 550 |
-
// Accumulate forces on both endpoint nodes
|
| 551 |
-
gpuMath.runProgram("beamForceCalc", {
|
| 552 |
-
u_position: positionTexture,
|
| 553 |
-
u_beamMeta: beamMetaTexture, // [nodeA, nodeB, restLen, stiffness]
|
| 554 |
-
}, forceTexture); // accumulates into force texture
|
| 555 |
-
|
| 556 |
-
// Step 3: Compute crease/hinge forces
|
| 557 |
-
// For each crease (fold + facet), compute rotational spring force
|
| 558 |
-
// Accumulate forces on all 4 nodes
|
| 559 |
-
gpuMath.runProgram("creaseForceCalc", {
|
| 560 |
-
u_position: positionTexture,
|
| 561 |
-
u_creaseMeta: creaseMetaTexture, // [n1, n2, n3, n4]
|
| 562 |
-
u_creaseAngles: creaseAngleTexture,
|
| 563 |
-
u_foldPercent: foldPercent,
|
| 564 |
-
}, forceTexture); // accumulates
|
| 565 |
-
|
| 566 |
-
// Step 4: Update positions via Verlet integration
|
| 567 |
-
gpuMath.runProgram("positionCalc", {
|
| 568 |
-
u_position: positionTexture,
|
| 569 |
-
u_lastPosition: lastPositionTexture,
|
| 570 |
-
u_force: forceTexture,
|
| 571 |
-
u_dt: dt,
|
| 572 |
-
u_damping: damping,
|
| 573 |
-
}, newPositionTexture);
|
| 574 |
-
|
| 575 |
-
// Step 5: Swap position buffers
|
| 576 |
-
var temp = lastPositionTexture;
|
| 577 |
-
lastPositionTexture = positionTexture;
|
| 578 |
-
positionTexture = newPositionTexture;
|
| 579 |
-
newPositionTexture = temp;
|
| 580 |
-
}
|
| 581 |
-
|
| 582 |
-
// Read back positions for rendering
|
| 583 |
-
gpuMath.readTexture(positionTexture, positionArray);
|
| 584 |
-
updateThreeJsGeometry(positionArray);
|
| 585 |
-
}
|
| 586 |
-
```
|
| 587 |
-
|
| 588 |
-
---
|
| 589 |
-
|
| 590 |
-
## 6. Strain Computation & Visualization
|
| 591 |
-
|
| 592 |
-
### How Strain Is Calculated
|
| 593 |
-
|
| 594 |
-
Strain is computed per-beam (edge) as engineering strain, then averaged per-face for visualization:
|
| 595 |
-
|
| 596 |
-
```glsl
|
| 597 |
-
// strainCalcShader.frag (reconstructed)
|
| 598 |
-
|
| 599 |
-
// Per beam: engineering strain
|
| 600 |
-
float strain_beam = abs(currentLength - restLength) / restLength;
|
| 601 |
-
|
| 602 |
-
// Per face: average strain of the face's three edges
|
| 603 |
-
float faceStrain = (strain_e0 + strain_e1 + strain_e2) / 3.0;
|
| 604 |
-
```
|
| 605 |
-
|
| 606 |
-
```javascript
|
| 607 |
-
// In the main code, strain per face:
|
| 608 |
-
function computeFaceStrain(faceIndex) {
|
| 609 |
-
var edges = getFaceEdges(faceIndex);
|
| 610 |
-
var totalStrain = 0;
|
| 611 |
-
for (var i = 0; i < edges.length; i++) {
|
| 612 |
-
var beam = edges[i];
|
| 613 |
-
var nodeA = position[beam.nodeA];
|
| 614 |
-
var nodeB = position[beam.nodeB];
|
| 615 |
-
var currentLen = distance(nodeA, nodeB);
|
| 616 |
-
var restLen = beam.restLength;
|
| 617 |
-
totalStrain += Math.abs(currentLen - restLen) / restLen;
|
| 618 |
-
}
|
| 619 |
-
return totalStrain / edges.length;
|
| 620 |
-
}
|
| 621 |
-
```
|
| 622 |
-
|
| 623 |
-
### Strain-to-Color Mapping
|
| 624 |
-
|
| 625 |
-
```javascript
|
| 626 |
-
// Strain visualization color mapping:
|
| 627 |
-
// strain = 0.0 --> blue (no strain, faces undistorted)
|
| 628 |
-
// strain = max --> red (maximum strain, faces stretched/compressed)
|
| 629 |
-
//
|
| 630 |
-
// The mapping uses a HSL gradient:
|
| 631 |
-
// hue: 240 (blue) to 0 (red)
|
| 632 |
-
// saturation: 1.0 (fully saturated)
|
| 633 |
-
// lightness: 0.5
|
| 634 |
-
|
| 635 |
-
function strainToColor(strain, maxStrain) {
|
| 636 |
-
var normalizedStrain = Math.min(strain / maxStrain, 1.0);
|
| 637 |
-
|
| 638 |
-
// HSL interpolation: blue (240) -> red (0)
|
| 639 |
-
var hue = (1.0 - normalizedStrain) * 240;
|
| 640 |
-
|
| 641 |
-
return hslToRgb(hue / 360, 1.0, 0.5);
|
| 642 |
-
}
|
| 643 |
-
|
| 644 |
-
// In the shader version (for GPU rendering):
|
| 645 |
-
// vec3 color = vec3(strain, 0.0, 1.0 - strain); // simplified R/B interpolation
|
| 646 |
-
```
|
| 647 |
-
|
| 648 |
-
### What Strain Tells You
|
| 649 |
-
|
| 650 |
-
- **Zero strain**: The mesh is in its rest configuration — no edges are stretched or compressed. This is the ideal state for rigid origami.
|
| 651 |
-
- **Low strain** (blue): The fold is progressing well with minimal face distortion. The crease pattern is compatible.
|
| 652 |
-
- **High strain** (red): Faces are being stretched/compressed. This means either:
|
| 653 |
-
- The crease pattern is not rigidly foldable (faces MUST deform to accommodate the fold)
|
| 654 |
-
- The stiffness parameters are imbalanced
|
| 655 |
-
- Self-intersection is occurring
|
| 656 |
-
|
| 657 |
-
**For RL reward signals**: Strain is an excellent reward component. Low global strain = good crease pattern. High strain = bad crease pattern (not physically realizable with rigid panels).
|
| 658 |
-
|
| 659 |
-
---
|
| 660 |
-
|
| 661 |
-
## 7. Real FOLD File Examples
|
| 662 |
-
|
| 663 |
-
### Example 1: Simple Blintz Base from OrigamiSimulator
|
| 664 |
-
|
| 665 |
-
The `assets/fold/` directory contains several example FOLD files. Here is what a blintz-base crease pattern looks like:
|
| 666 |
-
|
| 667 |
-
```json
|
| 668 |
-
{
|
| 669 |
-
"file_spec": 1.1,
|
| 670 |
-
"file_creator": "Origami Simulator",
|
| 671 |
-
"file_classes": ["singleModel"],
|
| 672 |
-
"frame_title": "Blintz Base",
|
| 673 |
-
"frame_classes": ["creasePattern"],
|
| 674 |
-
"frame_attributes": ["2D"],
|
| 675 |
-
"vertices_coords": [
|
| 676 |
-
[0, 0],
|
| 677 |
-
[1, 0],
|
| 678 |
-
[1, 1],
|
| 679 |
-
[0, 1],
|
| 680 |
-
[0.5, 0.5],
|
| 681 |
-
[0.5, 0],
|
| 682 |
-
[1, 0.5],
|
| 683 |
-
[0.5, 1],
|
| 684 |
-
[0, 0.5]
|
| 685 |
-
],
|
| 686 |
-
"edges_vertices": [
|
| 687 |
-
[0, 5], [5, 1], [1, 6], [6, 2],
|
| 688 |
-
[2, 7], [7, 3], [3, 8], [8, 0],
|
| 689 |
-
[0, 4], [1, 4], [2, 4], [3, 4],
|
| 690 |
-
[5, 4], [6, 4], [7, 4], [8, 4]
|
| 691 |
-
],
|
| 692 |
-
"edges_assignment": [
|
| 693 |
-
"B", "B", "B", "B",
|
| 694 |
-
"B", "B", "B", "B",
|
| 695 |
-
"M", "V", "M", "V",
|
| 696 |
-
"V", "M", "V", "M"
|
| 697 |
-
],
|
| 698 |
-
"edges_foldAngle": [
|
| 699 |
-
0, 0, 0, 0,
|
| 700 |
-
0, 0, 0, 0,
|
| 701 |
-
-180, 180, -180, 180,
|
| 702 |
-
180, -180, 180, -180
|
| 703 |
-
],
|
| 704 |
-
"faces_vertices": [
|
| 705 |
-
[0, 5, 4], [5, 1, 4], [1, 6, 4], [6, 2, 4],
|
| 706 |
-
[2, 7, 4], [7, 3, 4], [3, 8, 4], [8, 0, 4]
|
| 707 |
-
]
|
| 708 |
-
}
|
| 709 |
-
```
|
| 710 |
-
|
| 711 |
-
**Statistics**: 9 vertices, 16 edges, 8 faces. This is a simplified bird base (blintz base).
|
| 712 |
-
|
| 713 |
-
### Example 2: Miura-ori (3x3 grid) from OrigamiSimulator
|
| 714 |
-
|
| 715 |
-
A Miura-ori pattern is a parametric tessellation. The simulator generates these programmatically:
|
| 716 |
-
|
| 717 |
-
```json
|
| 718 |
-
{
|
| 719 |
-
"file_spec": 1.1,
|
| 720 |
-
"file_creator": "Origami Simulator",
|
| 721 |
-
"frame_classes": ["creasePattern"],
|
| 722 |
-
"frame_attributes": ["2D"],
|
| 723 |
-
"vertices_coords": [
|
| 724 |
-
[0.0, 0.0], [1.0, 0.1], [2.0, 0.0], [3.0, 0.1],
|
| 725 |
-
[0.0, 1.0], [1.0, 0.9], [2.0, 1.0], [3.0, 0.9],
|
| 726 |
-
[0.0, 2.0], [1.0, 2.1], [2.0, 2.0], [3.0, 2.1],
|
| 727 |
-
[0.0, 3.0], [1.0, 2.9], [2.0, 3.0], [3.0, 2.9]
|
| 728 |
-
],
|
| 729 |
-
"edges_vertices": [
|
| 730 |
-
[0,1],[1,2],[2,3],
|
| 731 |
-
[4,5],[5,6],[6,7],
|
| 732 |
-
[8,9],[9,10],[10,11],
|
| 733 |
-
[12,13],[13,14],[14,15],
|
| 734 |
-
[0,4],[4,8],[8,12],
|
| 735 |
-
[1,5],[5,9],[9,13],
|
| 736 |
-
[2,6],[6,10],[10,14],
|
| 737 |
-
[3,7],[7,11],[11,15],
|
| 738 |
-
[1,4],[2,5],[3,6],
|
| 739 |
-
[5,8],[6,9],[7,10],
|
| 740 |
-
[9,12],[10,13],[11,14]
|
| 741 |
-
],
|
| 742 |
-
"edges_assignment": [
|
| 743 |
-
"B","B","B",
|
| 744 |
-
"M","M","M",
|
| 745 |
-
"V","V","V",
|
| 746 |
-
"B","B","B",
|
| 747 |
-
"B","M","V","B",
|
| 748 |
-
"V","M","V","M",
|
| 749 |
-
"V","M","V","M",
|
| 750 |
-
"V","V","V",
|
| 751 |
-
"V","V","V"
|
| 752 |
-
],
|
| 753 |
-
"faces_vertices": [
|
| 754 |
-
[0,1,5,4],[1,2,6,5],[2,3,7,6],
|
| 755 |
-
[4,5,9,8],[5,6,10,9],[6,7,11,10],
|
| 756 |
-
[8,9,13,12],[9,10,14,13],[10,11,15,14]
|
| 757 |
-
]
|
| 758 |
-
}
|
| 759 |
-
```
|
| 760 |
-
|
| 761 |
-
**Statistics**: 16 vertices, ~30 edges, 9 quad faces (which triangulate to 18 triangles). The zigzag y-offsets (+0.1, -0.1) are the Miura-ori angle parameter.
|
| 762 |
-
|
| 763 |
-
### Example 3: Waterbomb Base
|
| 764 |
-
|
| 765 |
-
```json
|
| 766 |
-
{
|
| 767 |
-
"file_spec": 1.1,
|
| 768 |
-
"frame_classes": ["creasePattern"],
|
| 769 |
-
"vertices_coords": [
|
| 770 |
-
[0, 0], [0.5, 0], [1, 0],
|
| 771 |
-
[0, 0.5], [0.5, 0.5], [1, 0.5],
|
| 772 |
-
[0, 1], [0.5, 1], [1, 1]
|
| 773 |
-
],
|
| 774 |
-
"edges_vertices": [
|
| 775 |
-
[0,1],[1,2],[2,5],[5,8],[8,7],[7,6],[6,3],[3,0],
|
| 776 |
-
[0,4],[2,4],[8,4],[6,4],
|
| 777 |
-
[1,4],[5,4],[7,4],[3,4]
|
| 778 |
-
],
|
| 779 |
-
"edges_assignment": [
|
| 780 |
-
"B","B","B","B","B","B","B","B",
|
| 781 |
-
"V","V","V","V",
|
| 782 |
-
"M","M","M","M"
|
| 783 |
-
],
|
| 784 |
-
"faces_vertices": [
|
| 785 |
-
[0,1,4],[1,2,4],[2,5,4],[5,8,4],
|
| 786 |
-
[8,7,4],[7,6,4],[6,3,4],[3,0,4]
|
| 787 |
-
]
|
| 788 |
-
}
|
| 789 |
-
```
|
| 790 |
-
|
| 791 |
-
**Statistics**: 9 vertices, 16 edges, 8 triangular faces. The waterbomb is degree-8 at the center vertex (vertex 4), with alternating M/V.
|
| 792 |
-
|
| 793 |
-
### Example 4: From the edemaine/fold Repository
|
| 794 |
-
|
| 795 |
-
The `edemaine/fold` repo (`examples/` directory) contains several example files including:
|
| 796 |
-
|
| 797 |
-
- `crane.fold` — traditional crane crease pattern
|
| 798 |
-
- `square-twist.fold` — twist fold tessellation
|
| 799 |
-
- Various test patterns for the FOLD spec
|
| 800 |
-
|
| 801 |
-
A typical crane crease pattern from ORIPA/FOLD tools:
|
| 802 |
-
|
| 803 |
-
```json
|
| 804 |
-
{
|
| 805 |
-
"file_spec": 1.1,
|
| 806 |
-
"file_creator": "ORIPA",
|
| 807 |
-
"file_classes": ["singleModel"],
|
| 808 |
-
"frame_title": "Crane",
|
| 809 |
-
"frame_classes": ["creasePattern"],
|
| 810 |
-
"frame_attributes": ["2D"],
|
| 811 |
-
"vertices_coords": [
|
| 812 |
-
[0, 0], [200, 0], [400, 0],
|
| 813 |
-
[0, 200], [200, 200], [400, 200],
|
| 814 |
-
[0, 400], [200, 400], [400, 400]
|
| 815 |
-
],
|
| 816 |
-
"edges_vertices": [[0,1],[1,2],[3,4],[4,5],[6,7],[7,8],
|
| 817 |
-
[0,3],[3,6],[1,4],[4,7],[2,5],[5,8],
|
| 818 |
-
[0,4],[4,8],[2,4],[4,6]],
|
| 819 |
-
"edges_assignment": ["B","B","B","M","B","B",
|
| 820 |
-
"B","B","V","V","B","B",
|
| 821 |
-
"M","M","V","V"],
|
| 822 |
-
"faces_vertices": [[0,1,4,3],[1,2,5,4],[3,4,7,6],[4,5,8,7]]
|
| 823 |
-
}
|
| 824 |
-
```
|
| 825 |
-
|
| 826 |
-
### Typical Model Complexity in Practice
|
| 827 |
-
|
| 828 |
-
| Model | Vertices | Edges | Faces | Triangulated Faces |
|
| 829 |
-
|-------|----------|-------|-------|--------------------|
|
| 830 |
-
| Simple base (blintz) | 9 | 16 | 8 | 8 |
|
| 831 |
-
| Waterbomb base | 9 | 16 | 8 | 8 |
|
| 832 |
-
| Traditional crane | 50-80 | 100-150 | 60-100 | 120-200 |
|
| 833 |
-
| Miura-ori 3x3 | 16 | ~30 | 9 | 18 |
|
| 834 |
-
| Miura-ori 10x10 | 121 | ~340 | 100 | 200 |
|
| 835 |
-
| Complex tessellation | 200-500 | 500-1500 | 300-1000 | 600-2000 |
|
| 836 |
-
| Extreme models | 1000+ | 3000+ | 2000+ | 4000+ |
|
| 837 |
-
|
| 838 |
-
**Key insight**: Even complex origami models rarely exceed a few thousand vertices. The GPU solver handles up to ~10,000 nodes at interactive rates.
|
| 839 |
-
|
| 840 |
-
---
|
| 841 |
-
|
| 842 |
-
## 8. Minimal FOLD Representation for Our RL Environment
|
| 843 |
-
|
| 844 |
-
### What We Actually Need
|
| 845 |
-
|
| 846 |
-
Based on how OrigamiSimulator uses FOLD, here is the minimal representation:
|
| 847 |
-
|
| 848 |
-
```python
|
| 849 |
-
# Minimal FOLD state for RL environment
|
| 850 |
-
minimal_fold = {
|
| 851 |
-
# REQUIRED - the geometry
|
| 852 |
-
"vertices_coords": [[x, y], ...], # 2D coords (flat crease pattern)
|
| 853 |
-
"edges_vertices": [[v_i, v_j], ...], # edge connectivity
|
| 854 |
-
"edges_assignment": ["M", "V", "B", ...], # fold type per edge
|
| 855 |
-
|
| 856 |
-
# RECOMMENDED - explicit angle targets
|
| 857 |
-
"edges_foldAngle": [-180, 180, 0, ...], # target fold angles (degrees)
|
| 858 |
-
|
| 859 |
-
# RECOMMENDED - explicit faces
|
| 860 |
-
"faces_vertices": [[v0, v1, v2, ...], ...], # face polygons (CCW)
|
| 861 |
-
|
| 862 |
-
# METADATA
|
| 863 |
-
"frame_classes": ["creasePattern"],
|
| 864 |
-
"frame_attributes": ["2D"],
|
| 865 |
-
}
|
| 866 |
-
```
|
| 867 |
-
|
| 868 |
-
### What We Can Skip
|
| 869 |
-
|
| 870 |
-
| Field | Skip? | Reason |
|
| 871 |
-
|-------|-------|--------|
|
| 872 |
-
| `faceOrders` | YES | Physics simulation handles layer ordering |
|
| 873 |
-
| `vertices_vertices` | YES | Recomputed from edges |
|
| 874 |
-
| `edges_faces` | YES | Recomputed from edges + faces |
|
| 875 |
-
| `faces_edges` | YES | Recomputed from faces + edges |
|
| 876 |
-
| `vertices_edges` | YES | Recomputed |
|
| 877 |
-
| `file_spec`, `file_creator` | YES | Metadata only |
|
| 878 |
-
|
| 879 |
-
### Python Data Structure for RL State
|
| 880 |
-
|
| 881 |
-
```python
|
| 882 |
-
import numpy as np
|
| 883 |
-
from dataclasses import dataclass
|
| 884 |
-
from typing import List, Optional
|
| 885 |
-
|
| 886 |
-
@dataclass
|
| 887 |
-
class OrigamiFOLDState:
|
| 888 |
-
"""Minimal FOLD state for RL environment."""
|
| 889 |
-
|
| 890 |
-
# Core geometry
|
| 891 |
-
vertices_coords: np.ndarray # shape (num_vertices, 2) or (num_vertices, 3)
|
| 892 |
-
edges_vertices: np.ndarray # shape (num_edges, 2), dtype int
|
| 893 |
-
edges_assignment: List[str] # length num_edges, values in {"M","V","B","F","U"}
|
| 894 |
-
|
| 895 |
-
# Optional but recommended
|
| 896 |
-
edges_foldAngle: Optional[np.ndarray] = None # shape (num_edges,), degrees
|
| 897 |
-
faces_vertices: Optional[List[List[int]]] = None # ragged list of vertex indices
|
| 898 |
-
|
| 899 |
-
def to_fold_json(self) -> dict:
|
| 900 |
-
"""Export as FOLD JSON dict."""
|
| 901 |
-
fold = {
|
| 902 |
-
"file_spec": 1.1,
|
| 903 |
-
"frame_classes": ["creasePattern"],
|
| 904 |
-
"frame_attributes": ["2D"],
|
| 905 |
-
"vertices_coords": self.vertices_coords.tolist(),
|
| 906 |
-
"edges_vertices": self.edges_vertices.tolist(),
|
| 907 |
-
"edges_assignment": self.edges_assignment,
|
| 908 |
-
}
|
| 909 |
-
if self.edges_foldAngle is not None:
|
| 910 |
-
fold["edges_foldAngle"] = self.edges_foldAngle.tolist()
|
| 911 |
-
if self.faces_vertices is not None:
|
| 912 |
-
fold["faces_vertices"] = self.faces_vertices
|
| 913 |
-
return fold
|
| 914 |
-
|
| 915 |
-
@classmethod
|
| 916 |
-
def from_fold_json(cls, data: dict) -> "OrigamiFOLDState":
|
| 917 |
-
"""Import from FOLD JSON dict."""
|
| 918 |
-
coords = np.array(data["vertices_coords"], dtype=np.float64)
|
| 919 |
-
if coords.shape[1] == 2:
|
| 920 |
-
coords = np.hstack([coords, np.zeros((len(coords), 1))])
|
| 921 |
-
edges = np.array(data["edges_vertices"], dtype=np.int32)
|
| 922 |
-
assignments = data.get("edges_assignment", ["U"] * len(edges))
|
| 923 |
-
fold_angles = None
|
| 924 |
-
if "edges_foldAngle" in data:
|
| 925 |
-
fold_angles = np.array(data["edges_foldAngle"], dtype=np.float64)
|
| 926 |
-
faces = data.get("faces_vertices", None)
|
| 927 |
-
return cls(
|
| 928 |
-
vertices_coords=coords,
|
| 929 |
-
edges_vertices=edges,
|
| 930 |
-
edges_assignment=assignments,
|
| 931 |
-
edges_foldAngle=fold_angles,
|
| 932 |
-
faces_vertices=faces,
|
| 933 |
-
)
|
| 934 |
-
|
| 935 |
-
@property
|
| 936 |
-
def num_vertices(self) -> int:
|
| 937 |
-
return len(self.vertices_coords)
|
| 938 |
-
|
| 939 |
-
@property
|
| 940 |
-
def num_edges(self) -> int:
|
| 941 |
-
return len(self.edges_vertices)
|
| 942 |
-
|
| 943 |
-
@property
|
| 944 |
-
def num_mountain(self) -> int:
|
| 945 |
-
return self.edges_assignment.count("M")
|
| 946 |
-
|
| 947 |
-
@property
|
| 948 |
-
def num_valley(self) -> int:
|
| 949 |
-
return self.edges_assignment.count("V")
|
| 950 |
-
|
| 951 |
-
@property
|
| 952 |
-
def num_boundary(self) -> int:
|
| 953 |
-
return self.edges_assignment.count("B")
|
| 954 |
-
```
|
| 955 |
-
|
| 956 |
-
### Observation Space Encoding for RL
|
| 957 |
-
|
| 958 |
-
```python
|
| 959 |
-
# How to encode FOLD state as a fixed-size observation for RL:
|
| 960 |
-
|
| 961 |
-
# Option 1: Adjacency + feature matrix (for GNN-based policies)
|
| 962 |
-
# Node features: [x, y, z, is_boundary, is_interior, degree]
|
| 963 |
-
# Edge features: [assignment_onehot(5), fold_angle, edge_length]
|
| 964 |
-
|
| 965 |
-
# Option 2: Flattened fixed-size grid (for MLP/CNN policies)
|
| 966 |
-
# Discretize the unit square into an NxN grid
|
| 967 |
-
# Each cell stores: [has_vertex, has_M_edge, has_V_edge, has_B_edge]
|
| 968 |
-
|
| 969 |
-
# Option 3: Variable-length sequence (for transformer policies)
|
| 970 |
-
# Sequence of edge tokens: [v_i, v_j, assignment, fold_angle]
|
| 971 |
-
```
|
| 972 |
-
|
| 973 |
-
---
|
| 974 |
-
|
| 975 |
-
## 9. Summary: How the Simulator Actually Works (End-to-End)
|
| 976 |
-
|
| 977 |
-
1. **Load FOLD file** -> parse `vertices_coords`, `edges_vertices`, `edges_assignment`, `faces_vertices`
|
| 978 |
-
2. **Triangulate** non-triangular faces via ear-clipping -> creates new internal edges
|
| 979 |
-
3. **Classify edges** -> fold creases (M/V with target angles), facet hinges (flat target, high stiffness), boundary (no constraint)
|
| 980 |
-
4. **Build GPU textures** -> pack node positions, beam params, crease params into RGBA float textures
|
| 981 |
-
5. **Run solver loop** (per frame, ~100 iterations):
|
| 982 |
-
- Compute beam forces (axial springs prevent stretching)
|
| 983 |
-
- Compute crease forces (rotational springs drive folding / enforce flatness)
|
| 984 |
-
- Verlet integration with damping to update positions
|
| 985 |
-
6. **Compute strain** -> per-edge engineering strain = |L - L0| / L0, averaged per face
|
| 986 |
-
7. **Render** -> Three.js mesh colored by strain (blue=zero, red=max)
|
| 987 |
-
8. **User adjusts `foldPercent`** (0 to 1) -> target angles scale linearly -> solver re-converges
|
| 988 |
-
|
| 989 |
-
### Key Numerical Details
|
| 990 |
-
|
| 991 |
-
- **Timestep**: dt = 0.02 (small for stability)
|
| 992 |
-
- **Damping**: 0.1 (overdamped to reach equilibrium quickly)
|
| 993 |
-
- **Iterations per frame**: 100 (enough for incremental convergence)
|
| 994 |
-
- **Stiffness ratio**: axial >> facet > fold (prevents stretching while allowing controlled folding)
|
| 995 |
-
- **Convergence criterion**: total kinetic energy < threshold
|
| 996 |
-
|
| 997 |
-
### For Our RL Environment
|
| 998 |
-
|
| 999 |
-
The critical takeaway: **we do NOT need GPU shaders**. For an RL training loop, a NumPy/JAX port of the bar-and-hinge solver is sufficient:
|
| 1000 |
-
|
| 1001 |
-
```python
|
| 1002 |
-
# Pseudocode for our NumPy port:
|
| 1003 |
-
|
| 1004 |
-
def simulate_fold(fold_state, fold_percent, n_steps=1000):
|
| 1005 |
-
"""Simulate folding and return final positions + strain."""
|
| 1006 |
-
pos = fold_state.vertices_coords.copy() # (N, 3)
|
| 1007 |
-
last_pos = pos.copy()
|
| 1008 |
-
dt = 0.02
|
| 1009 |
-
damping = 0.1
|
| 1010 |
-
|
| 1011 |
-
for step in range(n_steps):
|
| 1012 |
-
forces = np.zeros_like(pos)
|
| 1013 |
-
|
| 1014 |
-
# Beam forces (vectorized over all edges)
|
| 1015 |
-
forces += compute_beam_forces(pos, edges, rest_lengths, k_axial)
|
| 1016 |
-
|
| 1017 |
-
# Crease forces (vectorized over all creases)
|
| 1018 |
-
forces += compute_crease_forces(pos, creases, target_angles * fold_percent,
|
| 1019 |
-
k_fold, k_facet)
|
| 1020 |
-
|
| 1021 |
-
# Verlet integration
|
| 1022 |
-
new_pos = pos + (1 - damping) * (pos - last_pos) + forces * dt**2
|
| 1023 |
-
last_pos = pos
|
| 1024 |
-
pos = new_pos
|
| 1025 |
-
|
| 1026 |
-
strain = compute_strain(pos, edges, rest_lengths)
|
| 1027 |
-
return pos, strain
|
| 1028 |
-
```
|
| 1029 |
-
|
| 1030 |
-
This gives us the same physics as OrigamiSimulator but in Python, suitable for vectorized RL training with JAX/NumPy.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/physics.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
| 1 |
-
# Physics of Paper Folding
|
| 2 |
-
|
| 3 |
-
## Fundamental Theorems
|
| 4 |
-
|
| 5 |
-
### Kawasaki's Theorem (Angles)
|
| 6 |
-
- At a single vertex, flat-foldable iff alternating angle sums = 180 on each side
|
| 7 |
-
- **Necessary but not sufficient** for global flat-foldability
|
| 8 |
-
- Use as: reward penalty for violation
|
| 9 |
-
|
| 10 |
-
### Maekawa's Theorem (Mountain/Valley Count)
|
| 11 |
-
- At any flat-fold vertex: `|M - V| = 2`
|
| 12 |
-
- Total creases at a vertex must be **even**
|
| 13 |
-
- Use as: hard constraint in action validation
|
| 14 |
-
|
| 15 |
-
### Global Flat-Foldability
|
| 16 |
-
- **NP-complete** (Bern & Hayes, 1996)
|
| 17 |
-
- Hardness comes from determining valid **layer ordering**
|
| 18 |
-
- Local conditions (Kawasaki + Maekawa) necessary but not sufficient
|
| 19 |
-
- Use as: approximate via constraint satisfaction for reward
|
| 20 |
-
|
| 21 |
-
### Rigid Foldability
|
| 22 |
-
- Can it fold from flat to folded state with rigid panels (no face bending)?
|
| 23 |
-
- Critical for engineering: metal, plastic panels must be rigid
|
| 24 |
-
- Checked via: triangle-triangle intersection + rigid body constraints
|
| 25 |
-
- Use as: pass/fail validation check
|
| 26 |
-
|
| 27 |
-
## Layer Ordering
|
| 28 |
-
- When paper folds flat, layers stack — which face is above which?
|
| 29 |
-
- Must satisfy: no face penetration
|
| 30 |
-
- FOLD format: `faceOrders` triples `[f, g, s]`
|
| 31 |
-
- This is the NP-hard part — approximate for RL
|
| 32 |
-
|
| 33 |
-
## Simulation Approaches (Ranked for RL Use)
|
| 34 |
-
|
| 35 |
-
1. **Bar-and-hinge** (Ghassaei) — edges as bars, rotational springs at hinges. Fast. Best for RL.
|
| 36 |
-
2. **Rigid body + compliant creases** — rigid panels, torsional spring creases. Good middle ground.
|
| 37 |
-
3. **FEM** — full stress/strain tensor. Accurate but too slow for RL training loop.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/python_tools.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
| 1 |
-
# Python Libraries for Origami RL
|
| 2 |
-
|
| 3 |
-
## Origami-Specific
|
| 4 |
-
|
| 5 |
-
### rigid-origami (TOP PICK)
|
| 6 |
-
- **GitHub**: https://github.com/belalugaX/rigid-origami
|
| 7 |
-
- Gym environment for origami design as board game
|
| 8 |
-
- Has: PPO, MCTS, evolutionary, BFS, DFS agents
|
| 9 |
-
- Has: triangle-triangle intersection + kinematic validation
|
| 10 |
-
- Has: action masking, symmetry enforcement
|
| 11 |
-
- Output: PNG patterns, OBJ models, GIF animations
|
| 12 |
-
- Install: conda env + `pip install gym-rori`
|
| 13 |
-
|
| 14 |
-
### PyOri
|
| 15 |
-
- `pip3 install pyori`
|
| 16 |
-
- Graph-based crease pattern generation
|
| 17 |
-
- Huzita-Justin axiom implementation
|
| 18 |
-
- Flat-foldability checks
|
| 19 |
-
- FOLD + SVG export
|
| 20 |
-
- Dependencies: numpy, matplotlib, scipy
|
| 21 |
-
|
| 22 |
-
### FoldZ
|
| 23 |
-
- https://github.com/generic-github-user/FoldZ
|
| 24 |
-
- Early development, ambitious scope, not production-ready
|
| 25 |
-
|
| 26 |
-
## Supporting Libraries
|
| 27 |
-
|
| 28 |
-
| Library | What We Use It For |
|
| 29 |
-
|---------|-------------------|
|
| 30 |
-
| **numpy** | Core geometry, linear algebra |
|
| 31 |
-
| **scipy** | Constraint solving, optimization, spatial queries |
|
| 32 |
-
| **trimesh** | Mesh ops, collision detection, STL/OBJ I/O |
|
| 33 |
-
| **matplotlib** | 2D crease pattern visualization |
|
| 34 |
-
| **plotly** | 3D interactive visualization (Gradio-compatible) |
|
| 35 |
-
| **networkx** | Crease pattern graph analysis |
|
| 36 |
-
| **shapely** | 2D polygon operations, area calculations |
|
| 37 |
-
| **gymnasium** | RL environment API standard |
|
| 38 |
-
|
| 39 |
-
## Key Sources
|
| 40 |
-
|
| 41 |
-
- [Ghassaei Simulator](https://github.com/amandaghassaei/OrigamiSimulator) — MIT, JS/WebGL
|
| 42 |
-
- [FOLD format](https://github.com/edemaine/fold) — JSON standard
|
| 43 |
-
- [TreeMaker](https://langorigami.com/article/treemaker/) — crease pattern from tree diagrams
|
| 44 |
-
- [SWOMPS](https://github.com/zzhuyii/OrigamiSimulator) — MATLAB multi-physics reference
|
| 45 |
-
- [Tachi's tools](https://origami.c.u-tokyo.ac.jp/~tachi/software/) — foundational algorithms
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/rendering_research.md
DELETED
|
@@ -1,863 +0,0 @@
|
|
| 1 |
-
# Origami Rendering & Visualization Research
|
| 2 |
-
|
| 3 |
-
## 1. Existing Open-Source Origami Renderers
|
| 4 |
-
|
| 5 |
-
### 1.1 Amanda Ghassaei's Origami Simulator (Best-in-Class)
|
| 6 |
-
|
| 7 |
-
- **GitHub**: https://github.com/amandaghassaei/OrigamiSimulator
|
| 8 |
-
- **Live demo**: https://origamisimulator.org/
|
| 9 |
-
- **License**: MIT
|
| 10 |
-
- **Tech stack**: Three.js (rendering), GPU fragment shaders (simulation), FOLD format (data)
|
| 11 |
-
- **Paper**: "Fast, Interactive Origami Simulation using GPU Computation" (7OSME)
|
| 12 |
-
|
| 13 |
-
**How the simulation works:**
|
| 14 |
-
- Folds all creases simultaneously (not sequential folding)
|
| 15 |
-
- Iteratively solves for small displacements due to forces from creases
|
| 16 |
-
- Three constraint types: distance (no stretch/compress), face (no shear), angular (fold/flatten)
|
| 17 |
-
- Each constraint weighted by stiffness parameter
|
| 18 |
-
- All computation in GPU fragment shaders for real-time performance
|
| 19 |
-
- Target fold angle per crease: [-180, 180] degrees (positive=valley, negative=mountain)
|
| 20 |
-
|
| 21 |
-
**Strain visualization:**
|
| 22 |
-
- Maps strain to vertex colors: blue (no strain) to red (max strain)
|
| 23 |
-
- Strain = average percent deviation of distance constraints (Cauchy/engineering strain)
|
| 24 |
-
|
| 25 |
-
**I/O formats**: SVG, FOLD input; FOLD, STL, OBJ export
|
| 26 |
-
|
| 27 |
-
**Embedding in React:**
|
| 28 |
-
- Not published as an npm package; no React component wrapper exists
|
| 29 |
-
- Would need to fork and extract the core simulation + Three.js rendering
|
| 30 |
-
- The Three.js scene setup is tightly coupled to the app's DOM/UI
|
| 31 |
-
- **Best approach**: extract the GPU solver + mesh rendering and wrap in R3F
|
| 32 |
-
|
| 33 |
-
**External libraries used:**
|
| 34 |
-
- svgpath + path-data-polyfill (SVG parsing)
|
| 35 |
-
- FOLD library (internal data structure)
|
| 36 |
-
- Earcut (triangulation)
|
| 37 |
-
- Three.js (rendering)
|
| 38 |
-
|
| 39 |
-
### 1.2 OrigamiOdyssey (React + R3F -- Closest to What We Need)
|
| 40 |
-
|
| 41 |
-
- **GitHub**: https://github.com/robbwdoering/origamiodyssey
|
| 42 |
-
- **Tech stack**: React SPA, Three.js via react-three-fiber, hierarchical instructions
|
| 43 |
-
- **What it does**: Teaches origami via 3D simulations, step-through instructions, free rotation
|
| 44 |
-
- **Key relevance**: Proves that origami can be rendered well with R3F
|
| 45 |
-
- Already uses react-three-fiber to render a mesh representing paper
|
| 46 |
-
- Supports step-through fold sequences with 3D rotation
|
| 47 |
-
|
| 48 |
-
### 1.3 georgiee/origami (Three.js Origami Builder)
|
| 49 |
-
|
| 50 |
-
- **GitHub**: https://github.com/georgiee/origami
|
| 51 |
-
- **Demo**: https://georgiee.github.io/origami/
|
| 52 |
-
- **What it does**: Runtime origami folding rendered in Three.js
|
| 53 |
-
- Has an editor mode and view mode
|
| 54 |
-
- Not React-based, but demonstrates the mesh-folding-along-crease approach
|
| 55 |
-
|
| 56 |
-
### 1.4 flat-folder (Jason S. Ku -- Computation Tool)
|
| 57 |
-
|
| 58 |
-
- **GitHub**: https://github.com/origamimagiro/flat-folder
|
| 59 |
-
- **What it does**: Computes and analyzes valid flat-foldable states
|
| 60 |
-
- Renders three simultaneous views: crease pattern, x-ray overlap, folded state
|
| 61 |
-
- Supports FOLD, SVG, OPX, CP input formats
|
| 62 |
-
- Highlights Maekawa/Kawasaki violations with red circles
|
| 63 |
-
- **Not a library** -- standalone web app, not embeddable
|
| 64 |
-
- Useful as a reference for rendering conventions
|
| 65 |
-
|
| 66 |
-
### 1.5 Crease Pattern Editor (mwalczyk/crease)
|
| 67 |
-
|
| 68 |
-
- **GitHub**: https://github.com/mwalczyk/crease
|
| 69 |
-
- Vector-based SVG line drawing tool for crease patterns
|
| 70 |
-
- Geometric operations for diagramming
|
| 71 |
-
- Returns lists of changed nodes/edges for SVG element updates
|
| 72 |
-
|
| 73 |
-
### 1.6 Other Notable Projects
|
| 74 |
-
|
| 75 |
-
| Project | URL | Notes |
|
| 76 |
-
|---------|-----|-------|
|
| 77 |
-
| OriDomi | http://oridomi.com/ | CSS/DOM paper folding effects, not 3D simulation |
|
| 78 |
-
| paperfold.js | https://github.com/mrflix/paperfold | CSS3 transitions for paper fold UI effects |
|
| 79 |
-
| Origami3D | https://github.com/mariusGundersen/Origami3D | Simple 3D engine using 2D canvas, minimal |
|
| 80 |
-
| CurvedCreases | https://github.com/amandaghassaei/CurvedCreases | Amanda Ghassaei's curved crease simulator |
|
| 81 |
-
|
| 82 |
-
---
|
| 83 |
-
|
| 84 |
-
## 2. Three.js in React
|
| 85 |
-
|
| 86 |
-
### 2.1 @react-three/fiber (R3F)
|
| 87 |
-
|
| 88 |
-
- **GitHub**: https://github.com/pmndrs/react-three-fiber
|
| 89 |
-
- **Docs**: https://r3f.docs.pmnd.rs/
|
| 90 |
-
- The standard way to use Three.js in React
|
| 91 |
-
- Declarative scene graph via JSX
|
| 92 |
-
- `useFrame` hook for per-frame updates (animation, simulation stepping)
|
| 93 |
-
- `useThree` hook for accessing renderer, scene, camera
|
| 94 |
-
- Full Three.js API available through refs
|
| 95 |
-
|
| 96 |
-
**Key pattern for origami:**
|
| 97 |
-
```jsx
|
| 98 |
-
<Canvas gl={{ preserveDrawingBuffer: true }}>
|
| 99 |
-
<PerspectiveCamera makeDefault position={[0, 5, 10]} />
|
| 100 |
-
<OrbitControls />
|
| 101 |
-
<mesh ref={paperMeshRef}>
|
| 102 |
-
<bufferGeometry>
|
| 103 |
-
<bufferAttribute attach="attributes-position" ... />
|
| 104 |
-
<bufferAttribute attach="attributes-color" ... />
|
| 105 |
-
</bufferGeometry>
|
| 106 |
-
<meshStandardMaterial vertexColors side={DoubleSide} />
|
| 107 |
-
</mesh>
|
| 108 |
-
</Canvas>
|
| 109 |
-
```
|
| 110 |
-
|
| 111 |
-
### 2.2 @react-three/drei
|
| 112 |
-
|
| 113 |
-
- **GitHub**: https://github.com/pmndrs/drei
|
| 114 |
-
- **Docs**: https://drei.docs.pmnd.rs/
|
| 115 |
-
- Provides: `OrbitControls`, `PerspectiveCamera`, `Html`, `Line`, `Text`, `Grid`, `Environment`
|
| 116 |
-
- `OrbitControls` -- rotate, zoom, pan the 3D view (essential for our rotatable viewer)
|
| 117 |
-
- `Line` / `meshline` -- for rendering crease lines in 3D
|
| 118 |
-
|
| 119 |
-
### 2.3 Rendering a Foldable Mesh
|
| 120 |
-
|
| 121 |
-
**Approach: Vertex manipulation on BufferGeometry**
|
| 122 |
-
|
| 123 |
-
The paper mesh is a `BufferGeometry` with a position attribute. To simulate folding:
|
| 124 |
-
|
| 125 |
-
1. Define crease lines as edges in the mesh
|
| 126 |
-
2. For each fold operation, identify vertices on one side of the crease
|
| 127 |
-
3. Apply a rotation (quaternion) to those vertices around the crease line axis
|
| 128 |
-
4. Update the position buffer: `geometry.attributes.position.needsUpdate = true`
|
| 129 |
-
|
| 130 |
-
**Implementation pattern:**
|
| 131 |
-
```javascript
|
| 132 |
-
// For a fold along a crease line from point A to point B:
|
| 133 |
-
const axis = new THREE.Vector3().subVectors(B, A).normalize();
|
| 134 |
-
const quaternion = new THREE.Quaternion().setFromAxisAngle(axis, foldAngle);
|
| 135 |
-
|
| 136 |
-
for (let i = 0; i < vertexCount; i++) {
|
| 137 |
-
if (isOnFoldSide(vertices[i], creaseLine)) {
|
| 138 |
-
const v = new THREE.Vector3(positions[i*3], positions[i*3+1], positions[i*3+2]);
|
| 139 |
-
v.sub(A); // translate to crease origin
|
| 140 |
-
v.applyQuaternion(quaternion); // rotate
|
| 141 |
-
v.add(A); // translate back
|
| 142 |
-
positions[i*3] = v.x;
|
| 143 |
-
positions[i*3+1] = v.y;
|
| 144 |
-
positions[i*3+2] = v.z;
|
| 145 |
-
}
|
| 146 |
-
}
|
| 147 |
-
geometry.attributes.position.needsUpdate = true;
|
| 148 |
-
```
|
| 149 |
-
|
| 150 |
-
**For simultaneous folding (Ghassaei approach):**
|
| 151 |
-
- Use a spring/constraint solver running per-frame in `useFrame`
|
| 152 |
-
- Each crease exerts angular force toward its target fold angle
|
| 153 |
-
- Distance constraints prevent stretch, face constraints prevent shear
|
| 154 |
-
- This is more physically accurate but computationally heavier
|
| 155 |
-
- Could be done in a Web Worker or WASM for performance
|
| 156 |
-
|
| 157 |
-
### 2.4 Color-Coding Strain with Vertex Colors
|
| 158 |
-
|
| 159 |
-
**Three.js Lut (Lookup Table) class:**
|
| 160 |
-
- Import: `import { Lut } from 'three/addons/math/Lut.js'`
|
| 161 |
-
- Built-in colormaps: `rainbow`, `cooltowarm`, `blackbody`, `grayscale`
|
| 162 |
-
- For strain: use `cooltowarm` (blue to red) or create custom colormap
|
| 163 |
-
|
| 164 |
-
**Implementation:**
|
| 165 |
-
```javascript
|
| 166 |
-
const lut = new Lut('cooltowarm', 512);
|
| 167 |
-
lut.setMin(0); // no strain
|
| 168 |
-
lut.setMax(maxStrain);
|
| 169 |
-
|
| 170 |
-
// For each vertex, compute strain and get color
|
| 171 |
-
const colors = new Float32Array(vertexCount * 3);
|
| 172 |
-
for (let i = 0; i < vertexCount; i++) {
|
| 173 |
-
const strain = computeVertexStrain(i);
|
| 174 |
-
const color = lut.getColor(strain);
|
| 175 |
-
colors[i * 3] = color.r;
|
| 176 |
-
colors[i * 3 + 1] = color.g;
|
| 177 |
-
colors[i * 3 + 2] = color.b;
|
| 178 |
-
}
|
| 179 |
-
|
| 180 |
-
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
| 181 |
-
// Material must have vertexColors: true
|
| 182 |
-
```
|
| 183 |
-
|
| 184 |
-
**In R3F JSX:**
|
| 185 |
-
```jsx
|
| 186 |
-
<mesh>
|
| 187 |
-
<bufferGeometry>
|
| 188 |
-
<bufferAttribute
|
| 189 |
-
attach="attributes-position"
|
| 190 |
-
array={positions}
|
| 191 |
-
count={vertexCount}
|
| 192 |
-
itemSize={3}
|
| 193 |
-
/>
|
| 194 |
-
<bufferAttribute
|
| 195 |
-
attach="attributes-color"
|
| 196 |
-
array={colors}
|
| 197 |
-
count={vertexCount}
|
| 198 |
-
itemSize={3}
|
| 199 |
-
/>
|
| 200 |
-
</bufferGeometry>
|
| 201 |
-
<meshStandardMaterial vertexColors side={THREE.DoubleSide} />
|
| 202 |
-
</mesh>
|
| 203 |
-
```
|
| 204 |
-
|
| 205 |
-
**Alternative: Shader-based heatmap**
|
| 206 |
-
- Custom `ShaderMaterial` with a uniform for strain data
|
| 207 |
-
- Map strain to color in fragment shader
|
| 208 |
-
- More performant for large meshes, but more complex to implement
|
| 209 |
-
|
| 210 |
-
---
|
| 211 |
-
|
| 212 |
-
## 3. Lightweight Rendering Approach for RL
|
| 213 |
-
|
| 214 |
-
### 3.1 What We Need
|
| 215 |
-
|
| 216 |
-
| Requirement | Priority | Approach |
|
| 217 |
-
|-------------|----------|----------|
|
| 218 |
-
| Live view of current fold state | High | Client-side R3F in Gradio |
|
| 219 |
-
| Periodic screenshots for training logs | High | `canvas.toDataURL()` or server-side matplotlib |
|
| 220 |
-
| Recording folding animation | Medium | CCapture.js / MediaRecorder API |
|
| 221 |
-
| Strain heatmap overlay | High | Vertex colors with Lut |
|
| 222 |
-
| Crease pattern 2D view | High | SVG (inline or via React component) |
|
| 223 |
-
|
| 224 |
-
### 3.2 Server-Side Rendering Options
|
| 225 |
-
|
| 226 |
-
**Option A: matplotlib 3D (Simplest, Recommended for RL Training)**
|
| 227 |
-
- `mpl_toolkits.mplot3d` for basic 3D wireframe/surface plots
|
| 228 |
-
- Good enough for training logs and periodic state snapshots
|
| 229 |
-
- `fig.savefig()` for PNG export
|
| 230 |
-
- Works headlessly on any server
|
| 231 |
-
- No GPU required
|
| 232 |
-
|
| 233 |
-
**Option B: Plotly 3D**
|
| 234 |
-
- `plotly.graph_objects.Mesh3d` for 3D mesh rendering
|
| 235 |
-
- Interactive HTML output embeddable in Gradio
|
| 236 |
-
- `fig.write_image()` for static export (requires kaleido)
|
| 237 |
-
- Better looking than matplotlib, still no GPU needed
|
| 238 |
-
|
| 239 |
-
**Option C: Headless Three.js (Node.js)**
|
| 240 |
-
- Uses `headless-gl` library for offscreen WebGL
|
| 241 |
-
- Full Three.js rendering fidelity
|
| 242 |
-
- Significant caveats:
|
| 243 |
-
- Most servers lack GPU, so rendering uses CPU (slow)
|
| 244 |
-
- `headless-gl` has compatibility issues with newer Three.js
|
| 245 |
-
- Requires JSDOM or `three-universal` for DOM mocking
|
| 246 |
-
- Complex setup for questionable benefit
|
| 247 |
-
- **Not recommended** unless visual fidelity is critical server-side
|
| 248 |
-
|
| 249 |
-
**Option D: trimesh + pyrender (Python)**
|
| 250 |
-
- `trimesh` for mesh manipulation, `pyrender` for offscreen rendering
|
| 251 |
-
- Better 3D quality than matplotlib
|
| 252 |
-
- Supports headless via EGL or osmesa
|
| 253 |
-
- Good middle ground
|
| 254 |
-
|
| 255 |
-
**Recommendation: matplotlib for training, Plotly/R3F for demo UI**
|
| 256 |
-
|
| 257 |
-
### 3.3 Client-Side Rendering (Primary for Demo)
|
| 258 |
-
|
| 259 |
-
**React + Three.js via R3F:**
|
| 260 |
-
- Best visual quality, real-time interactivity
|
| 261 |
-
- OrbitControls for rotation, zoom, pan
|
| 262 |
-
- Vertex colors for strain heatmap
|
| 263 |
-
- Runs in user's browser (GPU-accelerated)
|
| 264 |
-
|
| 265 |
-
### 3.4 Screenshots from Three.js
|
| 266 |
-
|
| 267 |
-
**Configuration:**
|
| 268 |
-
```jsx
|
| 269 |
-
<Canvas gl={{ preserveDrawingBuffer: true, antialias: true, alpha: true }}>
|
| 270 |
-
```
|
| 271 |
-
|
| 272 |
-
**Capture method:**
|
| 273 |
-
```javascript
|
| 274 |
-
// Inside a component with access to useThree()
|
| 275 |
-
const { gl, scene, camera } = useThree();
|
| 276 |
-
|
| 277 |
-
function captureScreenshot() {
|
| 278 |
-
gl.render(scene, camera);
|
| 279 |
-
const dataURL = gl.domElement.toDataURL('image/png');
|
| 280 |
-
// Send to server or download
|
| 281 |
-
return dataURL;
|
| 282 |
-
}
|
| 283 |
-
```
|
| 284 |
-
|
| 285 |
-
**Important:** `toDataURL()` must be called before exiting the current event/frame. The `preserveDrawingBuffer: true` flag is required or you get a black image. Keeping it enabled has minor performance cost.
|
| 286 |
-
|
| 287 |
-
**High-resolution capture:**
|
| 288 |
-
```javascript
|
| 289 |
-
// Temporarily increase pixel ratio for hi-res screenshot
|
| 290 |
-
const originalPixelRatio = gl.getPixelRatio();
|
| 291 |
-
gl.setPixelRatio(window.devicePixelRatio * 2);
|
| 292 |
-
gl.setSize(width * 2, height * 2);
|
| 293 |
-
gl.render(scene, camera);
|
| 294 |
-
const dataURL = gl.domElement.toDataURL('image/png');
|
| 295 |
-
gl.setPixelRatio(originalPixelRatio);
|
| 296 |
-
gl.setSize(width, height);
|
| 297 |
-
```
|
| 298 |
-
|
| 299 |
-
### 3.5 Recording Folding Animation
|
| 300 |
-
|
| 301 |
-
**Option A: CCapture.js (Best for frame-perfect capture)**
|
| 302 |
-
- **GitHub**: https://github.com/spite/ccapture.js
|
| 303 |
-
- Hooks into `requestAnimationFrame`, `Date.now()`, `setTimeout`
|
| 304 |
-
- Forces constant time step for stutter-free recording
|
| 305 |
-
- Supports WebM, PNG/JPEG tar, GIF
|
| 306 |
-
- Usage:
|
| 307 |
-
```javascript
|
| 308 |
-
const capturer = new CCapture({ format: 'webm', framerate: 30, quality: 95 });
|
| 309 |
-
capturer.start();
|
| 310 |
-
// In render loop:
|
| 311 |
-
capturer.capture(canvas);
|
| 312 |
-
// When done:
|
| 313 |
-
capturer.stop();
|
| 314 |
-
capturer.save(); // triggers download
|
| 315 |
-
```
|
| 316 |
-
|
| 317 |
-
**Option B: use-capture (R3F wrapper for CCapture.js)**
|
| 318 |
-
- **GitHub**: https://github.com/gsimone/use-capture
|
| 319 |
-
- npm: `use-capture`
|
| 320 |
-
- Hook-based API for R3F
|
| 321 |
-
- Note: GIF export reportedly broken; WebM works
|
| 322 |
-
- Usage:
|
| 323 |
-
```javascript
|
| 324 |
-
const [bind, startRecording] = useCapture({ duration: 4, fps: 30 });
|
| 325 |
-
// <Canvas {...bind} gl={{ preserveDrawingBuffer: true }}>
|
| 326 |
-
```
|
| 327 |
-
|
| 328 |
-
**Option C: MediaRecorder API (Browser-native)**
|
| 329 |
-
- No library needed
|
| 330 |
-
- `canvas.captureStream(30)` + `MediaRecorder`
|
| 331 |
-
- Outputs WebM or MP4 (codec support varies by browser)
|
| 332 |
-
- Less control over frame timing than CCapture.js
|
| 333 |
-
- Usage:
|
| 334 |
-
```javascript
|
| 335 |
-
const stream = canvas.captureStream(30);
|
| 336 |
-
const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
|
| 337 |
-
recorder.start();
|
| 338 |
-
// Later:
|
| 339 |
-
recorder.stop();
|
| 340 |
-
recorder.ondataavailable = (e) => { /* save e.data blob */ };
|
| 341 |
-
```
|
| 342 |
-
|
| 343 |
-
**Option D: Server-side GIF from matplotlib frames**
|
| 344 |
-
- Generate PNG frames server-side with matplotlib
|
| 345 |
-
- Assemble with `imageio` or `PIL` into GIF
|
| 346 |
-
- Lower quality but works without browser
|
| 347 |
-
- Good for training log animations
|
| 348 |
-
|
| 349 |
-
---
|
| 350 |
-
|
| 351 |
-
## 4. Gradio + React in HuggingFace Spaces
|
| 352 |
-
|
| 353 |
-
### 4.1 Gradio gr.HTML with Custom JavaScript (Recommended for Hackathon)
|
| 354 |
-
|
| 355 |
-
Gradio 6 introduced powerful `gr.HTML` customization. This is the fastest path to embedding Three.js in a Gradio app.
|
| 356 |
-
|
| 357 |
-
**How it works:**
|
| 358 |
-
- `html_template`: Handlebars (`{{}}`) or JS expression (`${}`) syntax
|
| 359 |
-
- `css_template`: Scoped CSS for the component
|
| 360 |
-
- `js_on_load`: JavaScript that runs when the component loads
|
| 361 |
-
- `server_functions`: Python functions callable from JS via `await server.func_name()`
|
| 362 |
-
- `@children`: Placeholder for embedding other Gradio components
|
| 363 |
-
|
| 364 |
-
**Three.js in gr.HTML pattern:**
|
| 365 |
-
```python
|
| 366 |
-
import gradio as gr
|
| 367 |
-
|
| 368 |
-
class OrigamiViewer(gr.HTML):
|
| 369 |
-
def __init__(self, **kwargs):
|
| 370 |
-
super().__init__(
|
| 371 |
-
html_template="""
|
| 372 |
-
<div id="origami-3d" style="width:100%; height:500px;"></div>
|
| 373 |
-
""",
|
| 374 |
-
js_on_load="""
|
| 375 |
-
// Load Three.js from CDN
|
| 376 |
-
const script = document.createElement('script');
|
| 377 |
-
script.src = 'https://unpkg.com/three@0.160.0/build/three.module.js';
|
| 378 |
-
script.type = 'module';
|
| 379 |
-
// ... set up scene, camera, renderer, mesh
|
| 380 |
-
// Access props.value for fold state data from Python
|
| 381 |
-
""",
|
| 382 |
-
css_template="""
|
| 383 |
-
#origami-3d { border: 1px solid #ccc; border-radius: 8px; }
|
| 384 |
-
""",
|
| 385 |
-
server_functions=[get_fold_state],
|
| 386 |
-
**kwargs
|
| 387 |
-
)
|
| 388 |
-
```
|
| 389 |
-
|
| 390 |
-
**Data flow:**
|
| 391 |
-
- Python sets `props.value` with fold state data (JSON)
|
| 392 |
-
- JS reads `props.value` and updates the Three.js scene
|
| 393 |
-
- JS can call `await server.func_name(args)` to invoke Python functions
|
| 394 |
-
- JS can set `props.value = newData` and call `trigger()` to send data back
|
| 395 |
-
|
| 396 |
-
### 4.2 Gradio Custom Components (Full npm Package)
|
| 397 |
-
|
| 398 |
-
For a more polished, reusable component:
|
| 399 |
-
|
| 400 |
-
```bash
|
| 401 |
-
gradio cc create OrigamiViewer --template SimpleTextbox
|
| 402 |
-
```
|
| 403 |
-
|
| 404 |
-
This creates:
|
| 405 |
-
```
|
| 406 |
-
origami_viewer/
|
| 407 |
-
backend/ # Python component code
|
| 408 |
-
frontend/ # Svelte/JS component (can include React/Three.js)
|
| 409 |
-
demo/ # Example app
|
| 410 |
-
pyproject.toml
|
| 411 |
-
```
|
| 412 |
-
|
| 413 |
-
- Frontend is Svelte by default but can import any JS library
|
| 414 |
-
- Can include React + R3F as dependencies in the frontend build
|
| 415 |
-
- Published to PyPI, installable with `pip install`
|
| 416 |
-
- More work but produces a proper reusable component
|
| 417 |
-
|
| 418 |
-
### 4.3 Gradio Model3D Component (Built-in but Limited)
|
| 419 |
-
|
| 420 |
-
Gradio has a built-in `gr.Model3D` component:
|
| 421 |
-
```python
|
| 422 |
-
gr.Model3D(value="model.glb", label="3D View")
|
| 423 |
-
```
|
| 424 |
-
- Supports GLB, GLTF, OBJ, STL
|
| 425 |
-
- Has built-in viewer with rotation/zoom
|
| 426 |
-
- **Limitations**: No custom vertex colors, no animation, no strain overlay
|
| 427 |
-
- Could work for static final-state display only
|
| 428 |
-
|
| 429 |
-
### 4.4 Alternative: Static React App + FastAPI Backend
|
| 430 |
-
|
| 431 |
-
**Architecture:**
|
| 432 |
-
```
|
| 433 |
-
origami-space/
|
| 434 |
-
Dockerfile
|
| 435 |
-
backend/
|
| 436 |
-
app.py # FastAPI server (OpenEnv + API endpoints)
|
| 437 |
-
frontend/
|
| 438 |
-
src/
|
| 439 |
-
App.tsx # React app with R3F
|
| 440 |
-
dist/ # Built static files
|
| 441 |
-
```
|
| 442 |
-
|
| 443 |
-
**FastAPI serves both:**
|
| 444 |
-
```python
|
| 445 |
-
from fastapi.staticfiles import StaticFiles
|
| 446 |
-
app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")
|
| 447 |
-
```
|
| 448 |
-
|
| 449 |
-
**Pros:**
|
| 450 |
-
- Full React + R3F with all features (OrbitControls, vertex colors, animation)
|
| 451 |
-
- Complete control over UI layout
|
| 452 |
-
- No Gradio limitations
|
| 453 |
-
|
| 454 |
-
**Cons:**
|
| 455 |
-
- Doesn't use Gradio (may not match hackathon expectations)
|
| 456 |
-
- More setup work
|
| 457 |
-
- Need to handle WebSocket connection to OpenEnv manually
|
| 458 |
-
|
| 459 |
-
### 4.5 Alternative: Streamlit + Custom Component
|
| 460 |
-
|
| 461 |
-
- `streamlit.components.v1.components.html()` can embed HTML/JS
|
| 462 |
-
- `streamlit-stl` component exists for 3D model display
|
| 463 |
-
- Three.js can be embedded via HTML string injection
|
| 464 |
-
- Less mature than Gradio for HF Spaces
|
| 465 |
-
- **Not recommended** unless Gradio proves insufficient
|
| 466 |
-
|
| 467 |
-
### 4.6 Deployment on HuggingFace Spaces
|
| 468 |
-
|
| 469 |
-
**Docker Space (most flexible):**
|
| 470 |
-
```yaml
|
| 471 |
-
# README.md header
|
| 472 |
-
---
|
| 473 |
-
title: Origami RL
|
| 474 |
-
sdk: docker
|
| 475 |
-
app_port: 7860
|
| 476 |
-
---
|
| 477 |
-
```
|
| 478 |
-
|
| 479 |
-
**Static Space (for React-only frontend):**
|
| 480 |
-
```yaml
|
| 481 |
-
---
|
| 482 |
-
title: Origami Viewer
|
| 483 |
-
sdk: static
|
| 484 |
-
---
|
| 485 |
-
```
|
| 486 |
-
- Supports build steps: `npm run build`
|
| 487 |
-
- Serves built files directly
|
| 488 |
-
|
| 489 |
-
**Recommended approach**: Docker Space with FastAPI serving both the OpenEnv server and a Gradio UI that uses `gr.HTML` for the Three.js viewer.
|
| 490 |
-
|
| 491 |
-
---
|
| 492 |
-
|
| 493 |
-
## 5. Crease Pattern 2D Rendering
|
| 494 |
-
|
| 495 |
-
### 5.1 Standard Origami Conventions
|
| 496 |
-
|
| 497 |
-
| Edge Type | Color | Line Style | FOLD Assignment |
|
| 498 |
-
|-----------|-------|------------|-----------------|
|
| 499 |
-
| Mountain fold | Red | Dashed (dash-dot-dot) | "M" |
|
| 500 |
-
| Valley fold | Blue | Dashed | "V" |
|
| 501 |
-
| Boundary | Black | Solid | "B" |
|
| 502 |
-
| Unassigned | Gray | Dotted | "U" |
|
| 503 |
-
| Flat/crease | Gray | Solid thin | "F" |
|
| 504 |
-
|
| 505 |
-
**Note:** There is no universally standardized convention. The above is the most common in computational origami tools. Ghassaei's simulator uses: red=mountain, blue=valley, black=boundary. Flat-folder uses the same convention for SVG import.
|
| 506 |
-
|
| 507 |
-
**FOLD format edge assignments:**
|
| 508 |
-
- `edges_assignment`: Array of "M", "V", "B", "U", "F"
|
| 509 |
-
- `edges_foldAngle`: Array of numbers in [-180, 180] (positive=valley, negative=mountain, 0=flat/boundary)
|
| 510 |
-
|
| 511 |
-
### 5.2 SVG Rendering (Recommended for 2D View)
|
| 512 |
-
|
| 513 |
-
**Direct SVG in React:**
|
| 514 |
-
```jsx
|
| 515 |
-
function CreasePatternSVG({ vertices, edges, assignments }) {
|
| 516 |
-
const getEdgeStyle = (assignment) => {
|
| 517 |
-
switch (assignment) {
|
| 518 |
-
case 'M': return { stroke: '#e74c3c', strokeDasharray: '8,3,2,3', strokeWidth: 2 };
|
| 519 |
-
case 'V': return { stroke: '#3498db', strokeDasharray: '6,3', strokeWidth: 2 };
|
| 520 |
-
case 'B': return { stroke: '#2c3e50', strokeDasharray: 'none', strokeWidth: 2.5 };
|
| 521 |
-
case 'U': return { stroke: '#95a5a6', strokeDasharray: '2,4', strokeWidth: 1 };
|
| 522 |
-
case 'F': return { stroke: '#bdc3c7', strokeDasharray: 'none', strokeWidth: 0.5 };
|
| 523 |
-
}
|
| 524 |
-
};
|
| 525 |
-
|
| 526 |
-
return (
|
| 527 |
-
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
| 528 |
-
{edges.map(([v1, v2], i) => {
|
| 529 |
-
const style = getEdgeStyle(assignments[i]);
|
| 530 |
-
return (
|
| 531 |
-
<line
|
| 532 |
-
key={i}
|
| 533 |
-
x1={vertices[v1][0]} y1={vertices[v1][1]}
|
| 534 |
-
x2={vertices[v2][0]} y2={vertices[v2][1]}
|
| 535 |
-
{...style}
|
| 536 |
-
/>
|
| 537 |
-
);
|
| 538 |
-
})}
|
| 539 |
-
{vertices.map(([x, y], i) => (
|
| 540 |
-
<circle key={i} cx={x} cy={y} r={0.5} fill="#333" />
|
| 541 |
-
))}
|
| 542 |
-
</svg>
|
| 543 |
-
);
|
| 544 |
-
}
|
| 545 |
-
```
|
| 546 |
-
|
| 547 |
-
### 5.3 SVG in Python (Server-Side, for matplotlib/Gradio)
|
| 548 |
-
|
| 549 |
-
```python
|
| 550 |
-
import svgwrite
|
| 551 |
-
|
| 552 |
-
def render_crease_pattern_svg(fold_data: dict) -> str:
|
| 553 |
-
"""Render FOLD data as SVG string."""
|
| 554 |
-
vertices = fold_data['vertices_coords']
|
| 555 |
-
edges = fold_data['edges_vertices']
|
| 556 |
-
assignments = fold_data.get('edges_assignment', ['U'] * len(edges))
|
| 557 |
-
|
| 558 |
-
STYLES = {
|
| 559 |
-
'M': {'stroke': 'red', 'stroke_dasharray': '8,3,2,3', 'stroke_width': 2},
|
| 560 |
-
'V': {'stroke': 'blue', 'stroke_dasharray': '6,3', 'stroke_width': 2},
|
| 561 |
-
'B': {'stroke': 'black', 'stroke_dasharray': 'none', 'stroke_width': 2.5},
|
| 562 |
-
'U': {'stroke': 'gray', 'stroke_dasharray': '2,4', 'stroke_width': 1},
|
| 563 |
-
'F': {'stroke': 'lightgray', 'stroke_dasharray': 'none', 'stroke_width': 0.5},
|
| 564 |
-
}
|
| 565 |
-
|
| 566 |
-
dwg = svgwrite.Drawing(size=('400px', '400px'), viewBox='0 0 1 1')
|
| 567 |
-
for (v1, v2), asgn in zip(edges, assignments):
|
| 568 |
-
style = STYLES.get(asgn, STYLES['U'])
|
| 569 |
-
dwg.add(dwg.line(
|
| 570 |
-
start=vertices[v1], end=vertices[v2],
|
| 571 |
-
**style
|
| 572 |
-
))
|
| 573 |
-
return dwg.tostring()
|
| 574 |
-
```
|
| 575 |
-
|
| 576 |
-
### 5.4 matplotlib for 2D Crease Pattern (Quickest Python Path)
|
| 577 |
-
|
| 578 |
-
```python
|
| 579 |
-
import matplotlib.pyplot as plt
|
| 580 |
-
import matplotlib.lines as mlines
|
| 581 |
-
|
| 582 |
-
def plot_crease_pattern(fold_data, ax=None):
|
| 583 |
-
if ax is None:
|
| 584 |
-
fig, ax = plt.subplots(1, 1, figsize=(6, 6))
|
| 585 |
-
|
| 586 |
-
vertices = fold_data['vertices_coords']
|
| 587 |
-
edges = fold_data['edges_vertices']
|
| 588 |
-
assignments = fold_data.get('edges_assignment', ['U'] * len(edges))
|
| 589 |
-
|
| 590 |
-
STYLES = {
|
| 591 |
-
'M': dict(color='red', linestyle='--', linewidth=2),
|
| 592 |
-
'V': dict(color='blue', linestyle='-.', linewidth=2),
|
| 593 |
-
'B': dict(color='black', linestyle='-', linewidth=2.5),
|
| 594 |
-
'U': dict(color='gray', linestyle=':', linewidth=1),
|
| 595 |
-
'F': dict(color='lightgray', linestyle='-', linewidth=0.5),
|
| 596 |
-
}
|
| 597 |
-
|
| 598 |
-
for (v1, v2), asgn in zip(edges, assignments):
|
| 599 |
-
p1, p2 = vertices[v1], vertices[v2]
|
| 600 |
-
style = STYLES.get(asgn, STYLES['U'])
|
| 601 |
-
ax.plot([p1[0], p2[0]], [p1[1], p2[1]], **style)
|
| 602 |
-
|
| 603 |
-
ax.set_aspect('equal')
|
| 604 |
-
ax.set_title('Crease Pattern')
|
| 605 |
-
return ax
|
| 606 |
-
```
|
| 607 |
-
|
| 608 |
-
### 5.5 D3.js Option (Overkill for Our Needs)
|
| 609 |
-
|
| 610 |
-
D3.js could render crease patterns with full interactivity (hover tooltips, click-to-fold, zoom/pan), but:
|
| 611 |
-
- Adds another large dependency
|
| 612 |
-
- SVG-in-React is simpler and sufficient
|
| 613 |
-
- D3 is better for complex data visualization, not needed for line drawings
|
| 614 |
-
- **Skip D3 unless we need interactive editing of crease patterns**
|
| 615 |
-
|
| 616 |
-
---
|
| 617 |
-
|
| 618 |
-
## 6. UI Layout & Component Architecture
|
| 619 |
-
|
| 620 |
-
### 6.1 Proposed Layout
|
| 621 |
-
|
| 622 |
-
```
|
| 623 |
-
+------------------------------------------------------------------+
|
| 624 |
-
| ORIGAMI RL ENVIRONMENT [Material: ▼] |
|
| 625 |
-
+------------------------------------------------------------------+
|
| 626 |
-
| | |
|
| 627 |
-
| CREASE PATTERN (2D) | FOLDED STATE (3D) |
|
| 628 |
-
| | |
|
| 629 |
-
| [SVG View] | [Three.js R3F Canvas] |
|
| 630 |
-
| Mountain = red --- | OrbitControls (rotate/zoom) |
|
| 631 |
-
| Valley = blue -.- | Vertex colors = strain heatmap |
|
| 632 |
-
| Border = black ___ | DoubleSide material |
|
| 633 |
-
| | |
|
| 634 |
-
| Scale: 1m x 1m | Camera: perspective, 45deg |
|
| 635 |
-
| | |
|
| 636 |
-
+------------------------------------------------------------------+
|
| 637 |
-
| |
|
| 638 |
-
| FOLD SEQUENCE METRICS DASHBOARD |
|
| 639 |
-
| [Step 1] [Step 2] ... Compactness: 0.85 |
|
| 640 |
-
| [Play] [Pause] [Reset] Fold count: 12 |
|
| 641 |
-
| Progress: ████░░ 4/8 Max strain: 0.03 |
|
| 642 |
-
| Validity: PASS |
|
| 643 |
-
| Deploy ratio: 15.2x |
|
| 644 |
-
| |
|
| 645 |
-
+------------------------------------------------------------------+
|
| 646 |
-
```
|
| 647 |
-
|
| 648 |
-
### 6.2 Component Breakdown
|
| 649 |
-
|
| 650 |
-
**For Gradio + gr.HTML approach:**
|
| 651 |
-
|
| 652 |
-
| Component | Implementation | Data Source |
|
| 653 |
-
|-----------|---------------|-------------|
|
| 654 |
-
| 2D Crease Pattern | SVG via `gr.HTML` or `gr.Plot` (matplotlib) | `vertices_coords`, `edges_vertices`, `edges_assignment` from FOLD |
|
| 655 |
-
| 3D Folded State | Three.js via `gr.HTML` with `js_on_load` | Vertex positions (3D), face indices, strain values |
|
| 656 |
-
| Strain Heatmap | Vertex colors on 3D mesh (Lut colormap) | Per-vertex strain from physics engine |
|
| 657 |
-
| Fold Sequence | `gr.Slider` + `gr.Button` controls | Fold step index, triggers re-render |
|
| 658 |
-
| Metrics Dashboard | `gr.Dataframe` or `gr.JSON` | Dict of metric name -> value |
|
| 659 |
-
| Material Selector | `gr.Dropdown` | Enum of material presets |
|
| 660 |
-
|
| 661 |
-
**For standalone React approach:**
|
| 662 |
-
|
| 663 |
-
| Component | Library | Notes |
|
| 664 |
-
|-----------|---------|-------|
|
| 665 |
-
| `<CreasePatternView>` | React SVG | Inline SVG, responsive |
|
| 666 |
-
| `<FoldedStateView>` | R3F + drei | Canvas with OrbitControls, BufferGeometry |
|
| 667 |
-
| `<StrainOverlay>` | Three.js Lut | Vertex color attribute on mesh |
|
| 668 |
-
| `<FoldSequencePlayer>` | React state + R3F useFrame | Timeline slider, play/pause |
|
| 669 |
-
| `<MetricsDashboard>` | React (plain HTML/CSS) | Cards with numeric values |
|
| 670 |
-
| `<MaterialSelector>` | React select | Updates physics constants |
|
| 671 |
-
|
| 672 |
-
### 6.3 Data Flow
|
| 673 |
-
|
| 674 |
-
```
|
| 675 |
-
Python Backend (OpenEnv)
|
| 676 |
-
|
|
| 677 |
-
| WebSocket / REST API
|
| 678 |
-
| Sends: { vertices_coords, edges_vertices, edges_assignment,
|
| 679 |
-
| fold_angles, strain_per_vertex, metrics }
|
| 680 |
-
v
|
| 681 |
-
Frontend (Gradio gr.HTML or React App)
|
| 682 |
-
|
|
| 683 |
-
+-- 2D View: vertices_coords[:, :2] + edges -> SVG lines
|
| 684 |
-
|
|
| 685 |
-
+-- 3D View: vertices_coords[:, :3] -> BufferGeometry positions
|
| 686 |
-
| faces_vertices -> index buffer
|
| 687 |
-
| strain_per_vertex -> Lut -> vertex colors
|
| 688 |
-
|
|
| 689 |
-
+-- Metrics: metrics dict -> dashboard display
|
| 690 |
-
|
|
| 691 |
-
+-- Animation: fold_angles array per step -> interpolate in useFrame
|
| 692 |
-
```
|
| 693 |
-
|
| 694 |
-
---
|
| 695 |
-
|
| 696 |
-
## 7. FOLD Format Integration
|
| 697 |
-
|
| 698 |
-
### 7.1 FOLD JavaScript Library
|
| 699 |
-
|
| 700 |
-
- **GitHub**: https://github.com/edemaine/fold
|
| 701 |
-
- **npm**: `npm install fold`
|
| 702 |
-
- **CDN**: Available via unpkg
|
| 703 |
-
- **Spec**: https://edemaine.github.io/fold/doc/spec.html
|
| 704 |
-
|
| 705 |
-
**Key data fields we need:**
|
| 706 |
-
```json
|
| 707 |
-
{
|
| 708 |
-
"vertices_coords": [[0,0], [1,0], [1,1], [0,1]],
|
| 709 |
-
"edges_vertices": [[0,1], [1,2], [2,3], [3,0], [0,2]],
|
| 710 |
-
"edges_assignment": ["B", "B", "B", "B", "M"],
|
| 711 |
-
"edges_foldAngle": [0, 0, 0, 0, -180],
|
| 712 |
-
"faces_vertices": [[0,1,2], [0,2,3]]
|
| 713 |
-
}
|
| 714 |
-
```
|
| 715 |
-
|
| 716 |
-
**Parsing in Python:**
|
| 717 |
-
```python
|
| 718 |
-
import json
|
| 719 |
-
|
| 720 |
-
def load_fold(filepath):
|
| 721 |
-
with open(filepath) as f:
|
| 722 |
-
return json.load(f)
|
| 723 |
-
# It's just JSON!
|
| 724 |
-
```
|
| 725 |
-
|
| 726 |
-
**Parsing in JavaScript:**
|
| 727 |
-
```javascript
|
| 728 |
-
// It's just JSON
|
| 729 |
-
const foldData = JSON.parse(fileContents);
|
| 730 |
-
// Or use the FOLD library for manipulation:
|
| 731 |
-
import FOLD from 'fold';
|
| 732 |
-
FOLD.filter.collapseNearbyVertices(foldData, epsilon);
|
| 733 |
-
```
|
| 734 |
-
|
| 735 |
-
### 7.2 Converting Between Python Backend and JS Frontend
|
| 736 |
-
|
| 737 |
-
The FOLD format is JSON-native, making Python-to-JS data transfer trivial:
|
| 738 |
-
- Python: `json.dumps(fold_data)` -> send via WebSocket/API
|
| 739 |
-
- JavaScript: `JSON.parse(message)` -> use directly with Three.js
|
| 740 |
-
|
| 741 |
-
---
|
| 742 |
-
|
| 743 |
-
## 8. Recommended Implementation Plan
|
| 744 |
-
|
| 745 |
-
### Phase 1: Minimum Viable Rendering (Day 1)
|
| 746 |
-
|
| 747 |
-
1. **Python-side 2D crease pattern** with matplotlib
|
| 748 |
-
- `plot_crease_pattern(fold_data)` function
|
| 749 |
-
- Display in Gradio via `gr.Plot`
|
| 750 |
-
- Mountain=red dashed, Valley=blue dash-dot, Boundary=black solid
|
| 751 |
-
|
| 752 |
-
2. **Python-side 3D wireframe** with matplotlib mplot3d
|
| 753 |
-
- `plot_folded_state(fold_data)` function
|
| 754 |
-
- Basic wireframe of folded vertices
|
| 755 |
-
- Display in Gradio via `gr.Plot`
|
| 756 |
-
|
| 757 |
-
3. **Metrics as gr.JSON or gr.Dataframe**
|
| 758 |
-
|
| 759 |
-
### Phase 2: Interactive 3D Viewer (Day 2)
|
| 760 |
-
|
| 761 |
-
4. **Three.js viewer via gr.HTML**
|
| 762 |
-
- Load Three.js + OrbitControls from CDN in `js_on_load`
|
| 763 |
-
- Receive fold state as `props.value` (JSON)
|
| 764 |
-
- Render mesh with vertex colors for strain
|
| 765 |
-
- OrbitControls for rotation/zoom
|
| 766 |
-
|
| 767 |
-
5. **SVG crease pattern via gr.HTML**
|
| 768 |
-
- Render 2D crease pattern as inline SVG
|
| 769 |
-
- Update when fold state changes
|
| 770 |
-
|
| 771 |
-
### Phase 3: Animation & Polish (Day 3)
|
| 772 |
-
|
| 773 |
-
6. **Fold sequence animation**
|
| 774 |
-
- Slider to step through fold sequence
|
| 775 |
-
- Animate fold angle interpolation
|
| 776 |
-
- Play/pause controls
|
| 777 |
-
|
| 778 |
-
7. **Screenshot/recording**
|
| 779 |
-
- `canvas.toDataURL()` for screenshots
|
| 780 |
-
- MediaRecorder API for video (simpler than CCapture.js)
|
| 781 |
-
|
| 782 |
-
8. **Material visual differentiation**
|
| 783 |
-
- Different colors/textures for paper vs mylar vs aluminum
|
| 784 |
-
- Opacity/shininess changes based on material
|
| 785 |
-
|
| 786 |
-
---
|
| 787 |
-
|
| 788 |
-
## 9. Key Technical Risks & Mitigations
|
| 789 |
-
|
| 790 |
-
| Risk | Mitigation |
|
| 791 |
-
|------|------------|
|
| 792 |
-
| Three.js in gr.HTML is janky | Fall back to Plotly 3D mesh or static React app |
|
| 793 |
-
| Vertex manipulation perf for large meshes | Limit mesh resolution (100x100 grid max), use Web Worker |
|
| 794 |
-
| FOLD format conversion errors | Use FOLD JS library for validation, test with known patterns |
|
| 795 |
-
| HF Spaces doesn't support WebGL | All modern browsers support WebGL; provide matplotlib fallback |
|
| 796 |
-
| Animation recording in Gradio | Use server-side GIF generation with imageio as fallback |
|
| 797 |
-
| OrbitControls conflict with Gradio events | Isolate Three.js canvas, prevent event propagation |
|
| 798 |
-
|
| 799 |
-
---
|
| 800 |
-
|
| 801 |
-
## 10. Key Dependencies
|
| 802 |
-
|
| 803 |
-
### Python (Backend)
|
| 804 |
-
```
|
| 805 |
-
numpy # Mesh computation
|
| 806 |
-
matplotlib # 2D/3D fallback rendering
|
| 807 |
-
svgwrite # SVG crease pattern generation (optional)
|
| 808 |
-
imageio # GIF export from frames
|
| 809 |
-
plotly # Interactive 3D (optional fallback)
|
| 810 |
-
trimesh # Mesh utilities (optional)
|
| 811 |
-
```
|
| 812 |
-
|
| 813 |
-
### JavaScript (Frontend, loaded via CDN or npm)
|
| 814 |
-
```
|
| 815 |
-
three # 3D rendering engine
|
| 816 |
-
@react-three/fiber # React wrapper (if standalone React app)
|
| 817 |
-
@react-three/drei # Helpers (OrbitControls, etc.)
|
| 818 |
-
fold # FOLD format manipulation
|
| 819 |
-
ccapture.js # Animation recording (optional)
|
| 820 |
-
```
|
| 821 |
-
|
| 822 |
-
### For Gradio gr.HTML approach (no npm needed)
|
| 823 |
-
```html
|
| 824 |
-
<!-- Load from CDN in js_on_load -->
|
| 825 |
-
<script src="https://unpkg.com/three@0.160.0/build/three.module.js" type="module"></script>
|
| 826 |
-
<script src="https://unpkg.com/three@0.160.0/examples/jsm/controls/OrbitControls.js" type="module"></script>
|
| 827 |
-
```
|
| 828 |
-
|
| 829 |
-
---
|
| 830 |
-
|
| 831 |
-
## Sources
|
| 832 |
-
|
| 833 |
-
- [Origami Simulator - GitHub](https://github.com/amandaghassaei/OrigamiSimulator)
|
| 834 |
-
- [Origami Simulator - Live Demo](https://origamisimulator.org/)
|
| 835 |
-
- [OrigamiOdyssey - React + R3F Origami App](https://github.com/robbwdoering/origamiodyssey)
|
| 836 |
-
- [georgiee/origami - Three.js Origami Builder](https://github.com/georgiee/origami)
|
| 837 |
-
- [flat-folder - Flat-Foldable State Computation](https://github.com/origamimagiro/flat-folder)
|
| 838 |
-
- [crease - SVG Crease Pattern Editor](https://github.com/mwalczyk/crease)
|
| 839 |
-
- [FOLD File Format - GitHub](https://github.com/edemaine/fold)
|
| 840 |
-
- [FOLD Specification](https://edemaine.github.io/fold/doc/spec.html)
|
| 841 |
-
- [FOLD npm package](https://www.npmjs.com/package/fold)
|
| 842 |
-
- [react-three-fiber - GitHub](https://github.com/pmndrs/react-three-fiber)
|
| 843 |
-
- [drei - R3F Helpers](https://github.com/pmndrs/drei)
|
| 844 |
-
- [R3F Basic Animations Tutorial](https://r3f.docs.pmnd.rs/tutorials/basic-animations)
|
| 845 |
-
- [Three.js Lut Documentation](https://threejs.org/docs/examples/en/math/Lut.html)
|
| 846 |
-
- [Three.js Vertex Colors Lookup Table Example](https://threejs.org/examples/webgl_geometry_colors_lookuptable.html)
|
| 847 |
-
- [Three.js Heatmap on 3D Model - Forum](https://discourse.threejs.org/t/how-to-creating-heatmap-over-3d-model/52744)
|
| 848 |
-
- [Three.js Vertex Color BufferGeometry](https://dustinpfister.github.io/2023/01/20/threejs-buffer-geometry-attributes-color/)
|
| 849 |
-
- [Three.js Mesh Modifiers (Bend/Twist)](https://github.com/drawcall/threejs-mesh-modifiers)
|
| 850 |
-
- [CCapture.js - Canvas Animation Capture](https://github.com/spite/ccapture.js/)
|
| 851 |
-
- [use-capture - R3F Recording Hook](https://github.com/gsimone/use-capture)
|
| 852 |
-
- [Videos and GIFs with Three.js](https://janakiev.com/blog/videos-and-gifs-with-threejs/)
|
| 853 |
-
- [R3F Screenshot Discussion](https://github.com/pmndrs/react-three-fiber/discussions/2054)
|
| 854 |
-
- [Headless Three.js Rendering - Forum](https://discourse.threejs.org/t/headless-rendering/14401)
|
| 855 |
-
- [Gradio Custom HTML Components Guide](https://www.gradio.app/guides/custom-HTML-components)
|
| 856 |
-
- [Gradio Custom Components in Five Minutes](https://www.gradio.app/guides/custom-components-in-five-minutes)
|
| 857 |
-
- [Gradio gr.HTML One-Shot Apps](https://huggingface.co/blog/gradio-html-one-shot-apps)
|
| 858 |
-
- [Gradio Model3D Component](https://www.gradio.app/docs/gradio/model3d)
|
| 859 |
-
- [HuggingFace Static HTML Spaces](https://huggingface.co/docs/hub/spaces-sdks-static)
|
| 860 |
-
- [Create Static HF Space with React](https://blog.rednegra.net/2024/10/14/create-a-static-huggingface-space-with-react)
|
| 861 |
-
- [Streamlit + R3F Discussion](https://discuss.streamlit.io/t/streamlit-components-wrap-around-react-three-fiber/4749)
|
| 862 |
-
- [OriDomi - CSS Paper Folding](http://oridomi.com/)
|
| 863 |
-
- [Fast, Interactive Origami Simulation using GPU Computation (Paper)](https://erikdemaine.org/papers/OrigamiSimulator_Origami7/paper.pdf)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/origami/simulation_engines.md
DELETED
|
@@ -1,65 +0,0 @@
|
|
| 1 |
-
# Origami Simulation Engines
|
| 2 |
-
|
| 3 |
-
## Ghassaei's Origami Simulator (Top Pick)
|
| 4 |
-
|
| 5 |
-
- **URL**: https://origamisimulator.org/ | [GitHub](https://github.com/amandaghassaei/OrigamiSimulator)
|
| 6 |
-
- **License**: MIT | **Language**: JavaScript/WebGL
|
| 7 |
-
|
| 8 |
-
**Physics model (truss model):**
|
| 9 |
-
- Folds all creases simultaneously via iterative constraint solving
|
| 10 |
-
- Three constraint types:
|
| 11 |
-
- **Distance** — prevent stretching/compressing
|
| 12 |
-
- **Face** — prevent shearing
|
| 13 |
-
- **Angular** — fold or flatten the sheet
|
| 14 |
-
- Each constraint has a **stiffness parameter** (higher = more rigid)
|
| 15 |
-
- All computation runs in **GPU fragment shaders** for real-time
|
| 16 |
-
|
| 17 |
-
**Strain visualization:**
|
| 18 |
-
- Blue (no strain) → Red (max strain)
|
| 19 |
-
- Strain = avg percent deviation of distance constraints (Cauchy/engineering strain)
|
| 20 |
-
|
| 21 |
-
**I/O**: SVG, FOLD → FOLD, STL, OBJ
|
| 22 |
-
|
| 23 |
-
**For us**: Port the truss model physics to NumPy. The constraint system maps directly to our reward signal.
|
| 24 |
-
|
| 25 |
-
---
|
| 26 |
-
|
| 27 |
-
## rigid-origami (Existing RL Gym Environment)
|
| 28 |
-
|
| 29 |
-
- **GitHub**: https://github.com/belalugaX/rigid-origami
|
| 30 |
-
- **Paper**: IJCAI 2023 — "Automating Rigid Origami Design"
|
| 31 |
-
|
| 32 |
-
Already has Gym API, PPO, MCTS, evolutionary agents. Formulates origami as a board game on a grid. Validation via triangle-triangle intersection + kinematic checks.
|
| 33 |
-
|
| 34 |
-
**For us**: Study the validation logic. The grid-based vertex placement can be our starting point.
|
| 35 |
-
|
| 36 |
-
---
|
| 37 |
-
|
| 38 |
-
## SWOMPS (Multi-Physics, MATLAB)
|
| 39 |
-
|
| 40 |
-
- **GitHub**: https://github.com/zzhuyii/OrigamiSimulator
|
| 41 |
-
- Large deformation + heat transfer + contact detection
|
| 42 |
-
- Five different solvers
|
| 43 |
-
- Companion dataset generator: https://github.com/zzhuyii/GenerateOrigamiDataSet
|
| 44 |
-
|
| 45 |
-
**For us**: Reference for stress/contact physics only. Not directly usable (MATLAB).
|
| 46 |
-
|
| 47 |
-
---
|
| 48 |
-
|
| 49 |
-
## Tachi's Software (Foundational, Proprietary)
|
| 50 |
-
|
| 51 |
-
- Rigid Origami Simulator, Freeform Origami, Origamizer
|
| 52 |
-
- **Origamizer**: universal — can fold ANY 3D polyhedral surface
|
| 53 |
-
- Windows-only, proprietary
|
| 54 |
-
|
| 55 |
-
**For us**: Algorithms are key references. Origamizer algorithm (Tachi & Demaine, SoCG 2017) proves universality.
|
| 56 |
-
|
| 57 |
-
---
|
| 58 |
-
|
| 59 |
-
## Others (Low Priority)
|
| 60 |
-
|
| 61 |
-
| Tool | Language | Notes |
|
| 62 |
-
|------|----------|-------|
|
| 63 |
-
| Origami Editor 3D | Java | GUI editor |
|
| 64 |
-
| Oriedita | Java | Crease pattern editor, FOLD I/O |
|
| 65 |
-
| VPython Origami | Python | Minimal, unmaintained |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/plan/architecture.md
DELETED
|
@@ -1,474 +0,0 @@
|
|
| 1 |
-
# Origami RL Environment — Final Architecture
|
| 2 |
-
|
| 3 |
-
## The Idea
|
| 4 |
-
|
| 5 |
-
An OpenEnv environment where an LLM learns to design optimal fold patterns for real-world folding problems — solar panel packing, medical stent deployment, shelter construction. The LLM generates a `fold_strategy()` function (code-as-policy, same as the 2048 pattern), which is executed against our origami simulation engine.
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## 1. Action Space
|
| 10 |
-
|
| 11 |
-
Based on the Huzita-Justin axioms and fold type research, we use a **named-fold-level** action space (not raw axioms — too low-level for LLM code generation).
|
| 12 |
-
|
| 13 |
-
### Fold Operations Available to the Agent
|
| 14 |
-
|
| 15 |
-
```python
|
| 16 |
-
class FoldAction:
|
| 17 |
-
fold_type: str # "valley", "mountain", "reverse_inside", "reverse_outside",
|
| 18 |
-
# "squash", "petal", "pleat", "crimp"
|
| 19 |
-
fold_line: FoldLine # Defined by two points OR angle + offset from edge
|
| 20 |
-
fold_angle: float # 0-180 degrees (how far to fold)
|
| 21 |
-
layer_select: str # "top", "all", "specific" (which layers to fold)
|
| 22 |
-
```
|
| 23 |
-
|
| 24 |
-
### Simplified Action Space (Start Here)
|
| 25 |
-
|
| 26 |
-
For the **code-as-policy** approach, the LLM writes a function that returns a list of fold instructions:
|
| 27 |
-
|
| 28 |
-
```python
|
| 29 |
-
def fold_strategy(paper_state: dict) -> list[dict]:
|
| 30 |
-
"""
|
| 31 |
-
paper_state = {
|
| 32 |
-
"width": 1.0, "height": 1.0,
|
| 33 |
-
"material": {"name": "mylar", "thickness_mm": 0.05, "youngs_modulus_gpa": 4.0},
|
| 34 |
-
"vertices": [[x,y,z], ...],
|
| 35 |
-
"edges": [[v1,v2], ...],
|
| 36 |
-
"assignments": ["B","B",...], # current M/V/B assignments
|
| 37 |
-
"fold_angles": [0, 0, ...], # current fold angles in degrees
|
| 38 |
-
"num_layers_at_center": 1,
|
| 39 |
-
"bounding_box": {"x": 1.0, "y": 1.0, "z": 0.0},
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
Returns list of fold operations:
|
| 43 |
-
[
|
| 44 |
-
{"type": "valley", "line": {"start": [0,0.5], "end": [1,0.5]}, "angle": 180},
|
| 45 |
-
{"type": "mountain", "line": {"start": [0.5,0], "end": [0.5,0.5]}, "angle": 180},
|
| 46 |
-
...
|
| 47 |
-
]
|
| 48 |
-
"""
|
| 49 |
-
```
|
| 50 |
-
|
| 51 |
-
### Why This Works
|
| 52 |
-
|
| 53 |
-
- LLM can reason about geometry in natural language / code
|
| 54 |
-
- Fold operations map directly to the simulation engine
|
| 55 |
-
- The strategy function is self-contained (sandbox-safe)
|
| 56 |
-
- Same pattern as 2048 — proven with GRPO training
|
| 57 |
-
|
| 58 |
-
---
|
| 59 |
-
|
| 60 |
-
## 2. State Representation
|
| 61 |
-
|
| 62 |
-
Based on FOLD format (the standard) + bar-and-hinge physics model.
|
| 63 |
-
|
| 64 |
-
### Internal State (Simulation Engine)
|
| 65 |
-
|
| 66 |
-
```python
|
| 67 |
-
@dataclass
|
| 68 |
-
class PaperState:
|
| 69 |
-
# Geometry (FOLD format compatible)
|
| 70 |
-
vertices: np.ndarray # (N, 3) vertex positions
|
| 71 |
-
edges: np.ndarray # (E, 2) edge vertex indices
|
| 72 |
-
faces: list[list[int]] # face vertex indices
|
| 73 |
-
assignments: list[str] # ["M","V","B","F"] per edge
|
| 74 |
-
fold_angles: np.ndarray # (E,) current fold angle per edge, degrees
|
| 75 |
-
|
| 76 |
-
# Physics
|
| 77 |
-
rest_lengths: np.ndarray # (E,) original edge lengths
|
| 78 |
-
strain: np.ndarray # (N,) per-vertex Cauchy strain
|
| 79 |
-
energy: float # total elastic energy
|
| 80 |
-
|
| 81 |
-
# Layer tracking
|
| 82 |
-
face_orders: list[tuple] # [(f1, f2, +1/-1), ...] layer ordering
|
| 83 |
-
num_layers: int # max layer count
|
| 84 |
-
|
| 85 |
-
# Material
|
| 86 |
-
material: Material # thickness, Young's modulus, max_strain
|
| 87 |
-
|
| 88 |
-
# Metrics (computed after each fold)
|
| 89 |
-
bounding_box: np.ndarray # (3,) folded bounding box dimensions
|
| 90 |
-
deployment_ratio: float # folded_area / unfolded_area
|
| 91 |
-
is_valid: bool # no self-intersections, theorems satisfied
|
| 92 |
-
kawasaki_violation: float # sum of angle violations
|
| 93 |
-
maekawa_violation: float # sum of M-V violations
|
| 94 |
-
self_intersections: int # count of face-face penetrations
|
| 95 |
-
```
|
| 96 |
-
|
| 97 |
-
### Observation (What the LLM Sees via Prompt)
|
| 98 |
-
|
| 99 |
-
```python
|
| 100 |
-
class OrigamiObservation(Observation):
|
| 101 |
-
paper_state: dict # Serialized PaperState (simplified)
|
| 102 |
-
task: dict # What to optimize
|
| 103 |
-
metrics: dict # Current scores
|
| 104 |
-
fold_history: list[dict] # Previous folds applied
|
| 105 |
-
error: str | None # If last fold was invalid, why
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
### Text Prompt Format
|
| 109 |
-
|
| 110 |
-
```
|
| 111 |
-
TASK: Fold a 1m x 1m Mylar sheet to minimize packed volume while maintaining deployability.
|
| 112 |
-
|
| 113 |
-
MATERIAL:
|
| 114 |
-
- Name: Mylar (space-grade)
|
| 115 |
-
- Thickness: 0.05mm
|
| 116 |
-
- Young's modulus: 4 GPa
|
| 117 |
-
- Max strain before failure: 3%
|
| 118 |
-
|
| 119 |
-
CONSTRAINTS:
|
| 120 |
-
- Must pack into bounding box <= 15cm x 15cm x 5cm
|
| 121 |
-
- Must deploy to >= 0.8m^2 area when unfolded
|
| 122 |
-
- Maximum 20 fold operations
|
| 123 |
-
- No self-intersections allowed
|
| 124 |
-
|
| 125 |
-
CURRENT STATE:
|
| 126 |
-
- Sheet: 1.0m x 1.0m, flat (0 folds applied)
|
| 127 |
-
- Bounding box: 1.0m x 1.0m x 0.0m
|
| 128 |
-
- Deployment ratio: 1.0 (fully deployed)
|
| 129 |
-
- Strain: 0.0 (no stress)
|
| 130 |
-
|
| 131 |
-
Write a fold_strategy(paper_state) function that returns a list of fold operations.
|
| 132 |
-
Each fold: {"type": "valley"|"mountain", "line": {"start": [x,y], "end": [x,y]}, "angle": 0-180}
|
| 133 |
-
```
|
| 134 |
-
|
| 135 |
-
---
|
| 136 |
-
|
| 137 |
-
## 3. Physics Engine
|
| 138 |
-
|
| 139 |
-
Based on the bar-and-hinge model (Ghassaei's approach, ported to NumPy).
|
| 140 |
-
|
| 141 |
-
### Module Layout
|
| 142 |
-
|
| 143 |
-
```
|
| 144 |
-
engine/
|
| 145 |
-
paper.py -> Paper data structure, FOLD I/O
|
| 146 |
-
fold_engine.py -> Apply fold operations to paper geometry
|
| 147 |
-
physics.py -> Bar-and-hinge energy computation, strain calculation
|
| 148 |
-
validation.py -> Kawasaki, Maekawa, self-intersection checks
|
| 149 |
-
metrics.py -> Deployment ratio, compactness, shape similarity
|
| 150 |
-
materials.py -> Material definitions (paper, mylar, aluminum, etc.)
|
| 151 |
-
```
|
| 152 |
-
|
| 153 |
-
### Fold Execution Pipeline
|
| 154 |
-
|
| 155 |
-
```
|
| 156 |
-
Input: FoldAction (type, line, angle)
|
| 157 |
-
|
|
| 158 |
-
+-- 1. Validate fold line (does it intersect the paper?)
|
| 159 |
-
+-- 2. Determine affected vertices (which side of fold line)
|
| 160 |
-
+-- 3. Apply rotation to affected vertices
|
| 161 |
-
| - Rotate around fold line by fold_angle
|
| 162 |
-
| - Using quaternion rotation for numerical stability
|
| 163 |
-
+-- 4. Update edge assignments (M/V based on fold type)
|
| 164 |
-
+-- 5. Update fold angles array
|
| 165 |
-
+-- 6. Compute new face topology (split faces at fold line if needed)
|
| 166 |
-
+-- 7. Run physics step
|
| 167 |
-
| - Compute bar energies (stretching)
|
| 168 |
-
| - Compute facet hinge energies (panel bending)
|
| 169 |
-
| - Compute fold hinge energies (crease folding)
|
| 170 |
-
| - Iterative solver: minimize total energy
|
| 171 |
-
+-- 8. Compute strain per vertex
|
| 172 |
-
+-- 9. Check validity
|
| 173 |
-
| - Kawasaki theorem at all vertices
|
| 174 |
-
| - Maekawa theorem at all vertices
|
| 175 |
-
| - Self-intersection detection (triangle-triangle)
|
| 176 |
-
+-- 10. Update metrics
|
| 177 |
-
| - Bounding box, deployment ratio, layer count
|
| 178 |
-
|
|
| 179 |
-
Output: Updated PaperState + validity report
|
| 180 |
-
```
|
| 181 |
-
|
| 182 |
-
### Energy Formulation (from research)
|
| 183 |
-
|
| 184 |
-
```python
|
| 185 |
-
# Bar-and-hinge model: three energy components
|
| 186 |
-
E_total = E_bar + E_facet + E_fold
|
| 187 |
-
|
| 188 |
-
E_bar = sum_bars (1/2) * k_axial * (L - L0)^2 # stretching
|
| 189 |
-
E_facet = sum_facets (1/2) * k_facet * l * (theta - pi)^2 # panel bending
|
| 190 |
-
E_fold = sum_folds (1/2) * k_fold * l * (rho - rho_target)^2 # crease folding
|
| 191 |
-
|
| 192 |
-
# Stiffness parameters (from material properties)
|
| 193 |
-
k_axial = E * t * w / L0
|
| 194 |
-
k_facet = E * t^3 / (12 * (1 - nu^2))
|
| 195 |
-
k_fold = kappa # crease torsional stiffness
|
| 196 |
-
```
|
| 197 |
-
|
| 198 |
-
### Strain Computation (Ghassaei's formula)
|
| 199 |
-
|
| 200 |
-
```python
|
| 201 |
-
def compute_strain(vertices, edges, rest_lengths):
|
| 202 |
-
"""Per-vertex Cauchy strain = avg percent deviation of edge lengths."""
|
| 203 |
-
strain = np.zeros(len(vertices))
|
| 204 |
-
for v_idx in range(len(vertices)):
|
| 205 |
-
neighbor_edges = get_edges_at_vertex(v_idx, edges)
|
| 206 |
-
deviations = []
|
| 207 |
-
for e_idx in neighbor_edges:
|
| 208 |
-
v1, v2 = edges[e_idx]
|
| 209 |
-
L = np.linalg.norm(vertices[v1] - vertices[v2])
|
| 210 |
-
L0 = rest_lengths[e_idx]
|
| 211 |
-
deviations.append(abs(L - L0) / L0)
|
| 212 |
-
strain[v_idx] = np.mean(deviations) if deviations else 0.0
|
| 213 |
-
return strain
|
| 214 |
-
```
|
| 215 |
-
|
| 216 |
-
---
|
| 217 |
-
|
| 218 |
-
## 4. Reward Functions
|
| 219 |
-
|
| 220 |
-
Three reward functions (same pattern as 2048):
|
| 221 |
-
|
| 222 |
-
### Reward 1: `code_valid(completions)`
|
| 223 |
-
|
| 224 |
-
Does the LLM output compile and produce valid fold instructions?
|
| 225 |
-
|
| 226 |
-
| Condition | Score |
|
| 227 |
-
|-----------|-------|
|
| 228 |
-
| Valid function returning fold list | +1.0 |
|
| 229 |
-
| Correct structure but exec fails | -0.5 |
|
| 230 |
-
| No function / syntax error | -2.0 |
|
| 231 |
-
| Non-stdlib imports | -20.0 |
|
| 232 |
-
|
| 233 |
-
### Reward 2: `physically_valid(completions)`
|
| 234 |
-
|
| 235 |
-
Are the folds physically possible?
|
| 236 |
-
|
| 237 |
-
| Condition | Score |
|
| 238 |
-
|-----------|-------|
|
| 239 |
-
| All folds valid, no violations | +1.0 |
|
| 240 |
-
| Per Kawasaki violation | -2.0 each |
|
| 241 |
-
| Per Maekawa violation | -2.0 each |
|
| 242 |
-
| Any self-intersection | -5.0 |
|
| 243 |
-
| Strain exceeds material limit | -1.0 |
|
| 244 |
-
| Function broken / can't run | 0.0 |
|
| 245 |
-
|
| 246 |
-
### Reward 3: `fold_quality(completions)`
|
| 247 |
-
|
| 248 |
-
How good is the folding solution?
|
| 249 |
-
|
| 250 |
-
| Condition | Score |
|
| 251 |
-
|-----------|-------|
|
| 252 |
-
| Compactness (1 - deployment_ratio) | +20.0 * compactness |
|
| 253 |
-
| Meets volume constraint (fits in target box) | +10.0 bonus |
|
| 254 |
-
| Deployable (can unfold cleanly) | +5.0 bonus |
|
| 255 |
-
| Per fold (efficiency penalty) | -0.5 each |
|
| 256 |
-
| High strain (material stress) | -3.0 * (max_strain / limit) |
|
| 257 |
-
| Timeout (>5 sec) | -1.0 |
|
| 258 |
-
| Exception during execution | -3.0 |
|
| 259 |
-
|
| 260 |
-
---
|
| 261 |
-
|
| 262 |
-
## 5. OpenEnv Integration
|
| 263 |
-
|
| 264 |
-
### Server: OrigamiEnvironment
|
| 265 |
-
|
| 266 |
-
```python
|
| 267 |
-
class OrigamiEnvironment(Environment[OrigamiAction, OrigamiObservation, OrigamiState]):
|
| 268 |
-
|
| 269 |
-
def reset(self, seed=None, episode_id=None, **kwargs):
|
| 270 |
-
task = self._sample_task()
|
| 271 |
-
self._paper = create_flat_sheet(task["width"], task["height"], task["material"])
|
| 272 |
-
self._task = task
|
| 273 |
-
self._fold_history = []
|
| 274 |
-
return self._make_observation()
|
| 275 |
-
|
| 276 |
-
def step(self, action: OrigamiAction, **kwargs):
|
| 277 |
-
# Extract and sandbox the fold strategy code
|
| 278 |
-
strategy_fn = sandbox_execute(action.fold_code)
|
| 279 |
-
folds = strategy_fn(self._paper.to_dict())
|
| 280 |
-
|
| 281 |
-
# Apply each fold
|
| 282 |
-
for fold in folds:
|
| 283 |
-
result = self._engine.apply_fold(self._paper, fold)
|
| 284 |
-
if not result.valid:
|
| 285 |
-
return self._make_observation(error=result.error, done=True, reward=-5.0)
|
| 286 |
-
self._paper = result.new_state
|
| 287 |
-
self._fold_history.append(fold)
|
| 288 |
-
|
| 289 |
-
# Compute final metrics and reward
|
| 290 |
-
metrics = self._compute_metrics()
|
| 291 |
-
reward = self._compute_reward(metrics)
|
| 292 |
-
return self._make_observation(done=True, reward=reward)
|
| 293 |
-
```
|
| 294 |
-
|
| 295 |
-
### Task Pool (Curriculum)
|
| 296 |
-
|
| 297 |
-
```python
|
| 298 |
-
TASK_POOL = [
|
| 299 |
-
# Level 1: Simple folds
|
| 300 |
-
{"name": "half_fold", "width": 1.0, "height": 1.0,
|
| 301 |
-
"material": "paper", "target_ratio": 0.5, "max_folds": 3},
|
| 302 |
-
|
| 303 |
-
# Level 2: Multi-fold packing
|
| 304 |
-
{"name": "letter_fold", "width": 1.0, "height": 1.0,
|
| 305 |
-
"material": "paper", "target_ratio": 0.33, "max_folds": 5},
|
| 306 |
-
|
| 307 |
-
# Level 3: Miura-ori discovery
|
| 308 |
-
{"name": "solar_panel", "width": 1.0, "height": 1.0,
|
| 309 |
-
"material": "mylar", "target_ratio": 0.05, "max_folds": 20,
|
| 310 |
-
"must_deploy": True, "target_box": [0.15, 0.15, 0.05]},
|
| 311 |
-
|
| 312 |
-
# Level 4: Constrained engineering
|
| 313 |
-
{"name": "stent_fold", "width": 0.1, "height": 0.03,
|
| 314 |
-
"material": "nitinol", "target_shape": "cylinder",
|
| 315 |
-
"deployed_diameter": 0.01, "compressed_diameter": 0.003},
|
| 316 |
-
]
|
| 317 |
-
```
|
| 318 |
-
|
| 319 |
-
---
|
| 320 |
-
|
| 321 |
-
## 6. Rendering Pipeline
|
| 322 |
-
|
| 323 |
-
### Training Time (Server-Side, Headless)
|
| 324 |
-
|
| 325 |
-
```python
|
| 326 |
-
# matplotlib — fast, no GPU needed
|
| 327 |
-
def render_crease_pattern_2d(state) -> Image:
|
| 328 |
-
"""M=red dashed, V=blue dash-dot, B=black solid"""
|
| 329 |
-
|
| 330 |
-
def render_folded_3d(state) -> Image:
|
| 331 |
-
"""3D wireframe with strain colors (blue->red)"""
|
| 332 |
-
```
|
| 333 |
-
|
| 334 |
-
### Demo Time (Client-Side, React + Three.js)
|
| 335 |
-
|
| 336 |
-
```
|
| 337 |
-
React App (Docker Space on HF)
|
| 338 |
-
+-- CreasePatternPanel (SVG, left)
|
| 339 |
-
| +-- Edges colored by M/V/B assignment
|
| 340 |
-
+-- FoldedView3D (@react-three/fiber, right)
|
| 341 |
-
| +-- BufferGeometry with vertex colors (strain heatmap)
|
| 342 |
-
| +-- OrbitControls for rotation
|
| 343 |
-
| +-- Animation: step through fold sequence
|
| 344 |
-
+-- MetricsDashboard (bottom)
|
| 345 |
-
| +-- Compactness, fold count, strain, validity
|
| 346 |
-
+-- MaterialSelector (sidebar)
|
| 347 |
-
+-- Paper, Mylar, Aluminum, Nitinol
|
| 348 |
-
```
|
| 349 |
-
|
| 350 |
-
### Tech Stack
|
| 351 |
-
|
| 352 |
-
| Component | Library |
|
| 353 |
-
|-----------|---------|
|
| 354 |
-
| 3D scene | @react-three/fiber |
|
| 355 |
-
| Controls | @react-three/drei |
|
| 356 |
-
| Strain heatmap | Three.js Lut + vertex colors |
|
| 357 |
-
| 2D crease pattern | Inline SVG |
|
| 358 |
-
| Screenshots | canvas.toDataURL() |
|
| 359 |
-
| Recording | CCapture.js / MediaRecorder |
|
| 360 |
-
| HF Spaces | Docker Space (FastAPI + static React build) |
|
| 361 |
-
|
| 362 |
-
---
|
| 363 |
-
|
| 364 |
-
## 7. Project Structure
|
| 365 |
-
|
| 366 |
-
```
|
| 367 |
-
origami/
|
| 368 |
-
research/ # All research docs
|
| 369 |
-
research.md # Index
|
| 370 |
-
openenv/ # OpenEnv framework research
|
| 371 |
-
origami/ # Domain research
|
| 372 |
-
plan/ # Architecture docs
|
| 373 |
-
|
| 374 |
-
engine/ # Core simulation (numpy/scipy only)
|
| 375 |
-
__init__.py
|
| 376 |
-
paper.py # Paper data structure, FOLD I/O
|
| 377 |
-
fold_engine.py # Apply folds (quaternion rotation)
|
| 378 |
-
physics.py # Bar-and-hinge energy, strain
|
| 379 |
-
validation.py # Kawasaki, Maekawa, self-intersection
|
| 380 |
-
metrics.py # Deployment ratio, compactness
|
| 381 |
-
materials.py # Material definitions
|
| 382 |
-
|
| 383 |
-
environment/ # OpenEnv server
|
| 384 |
-
__init__.py
|
| 385 |
-
models.py # Action, Observation, State
|
| 386 |
-
origami_environment.py # Environment (reset/step/state)
|
| 387 |
-
tasks.py # Task pool / curriculum
|
| 388 |
-
app.py # create_app()
|
| 389 |
-
Dockerfile
|
| 390 |
-
requirements.txt
|
| 391 |
-
|
| 392 |
-
client/ # OpenEnv client + training bridge
|
| 393 |
-
__init__.py
|
| 394 |
-
client.py # EnvClient subclass
|
| 395 |
-
reward_functions.py # code_valid, physically_valid, fold_quality
|
| 396 |
-
|
| 397 |
-
renderer/ # Visualization
|
| 398 |
-
server_render.py # matplotlib headless
|
| 399 |
-
web/ # React app
|
| 400 |
-
package.json
|
| 401 |
-
src/
|
| 402 |
-
App.tsx
|
| 403 |
-
CreasePattern.tsx # 2D SVG view
|
| 404 |
-
FoldedView3D.tsx # R3F 3D view
|
| 405 |
-
StrainHeatmap.tsx # Vertex color mapping
|
| 406 |
-
FoldAnimation.tsx # Step-through animation
|
| 407 |
-
MetricsDashboard.tsx # Scores display
|
| 408 |
-
|
| 409 |
-
training/ # Colab notebook
|
| 410 |
-
train_origami.ipynb # GRPO training (Unsloth + TRL)
|
| 411 |
-
prompts.py # LLM prompt templates
|
| 412 |
-
|
| 413 |
-
openenv.yaml # Manifest
|
| 414 |
-
pyproject.toml
|
| 415 |
-
README.md
|
| 416 |
-
```
|
| 417 |
-
|
| 418 |
-
---
|
| 419 |
-
|
| 420 |
-
## 8. Implementation Order
|
| 421 |
-
|
| 422 |
-
### Phase 1: Engine (first)
|
| 423 |
-
1. `paper.py` — Paper class, flat sheet creation, FOLD JSON serialize
|
| 424 |
-
2. `fold_engine.py` — Valley/mountain folds via quaternion rotation
|
| 425 |
-
3. `validation.py` — Kawasaki check, Maekawa check, basic self-intersection
|
| 426 |
-
4. `metrics.py` — Bounding box, deployment ratio, fold count
|
| 427 |
-
5. Test: fold a sheet in half, verify metrics
|
| 428 |
-
|
| 429 |
-
### Phase 2: OpenEnv Server
|
| 430 |
-
1. `models.py` — Pydantic models
|
| 431 |
-
2. `origami_environment.py` — reset/step
|
| 432 |
-
3. `app.py` + `Dockerfile`
|
| 433 |
-
4. `tasks.py` — 3-4 tasks
|
| 434 |
-
5. Test: curl the server, verify reset/step
|
| 435 |
-
|
| 436 |
-
### Phase 3: Reward + Training
|
| 437 |
-
1. `reward_functions.py` — three reward functions
|
| 438 |
-
2. `prompts.py` — prompt template
|
| 439 |
-
3. `train_origami.ipynb` — Colab GRPO notebook
|
| 440 |
-
4. Test: few training steps, verify rewards
|
| 441 |
-
|
| 442 |
-
### Phase 4: Rendering + Demo
|
| 443 |
-
1. `server_render.py` — matplotlib 2D + 3D
|
| 444 |
-
2. React app scaffold — R3F + SVG
|
| 445 |
-
3. Docker Space deployment
|
| 446 |
-
4. Record 1-minute demo video
|
| 447 |
-
|
| 448 |
-
---
|
| 449 |
-
|
| 450 |
-
## 9. Decisions (Locked)
|
| 451 |
-
|
| 452 |
-
| Decision | Choice | Why |
|
| 453 |
-
|----------|--------|-----|
|
| 454 |
-
| LLM interaction | Code-as-policy | Proven with 2048, hackathon expects it |
|
| 455 |
-
| Action space | Named fold ops + line + angle | Right level for LLM code gen |
|
| 456 |
-
| State format | FOLD-compatible JSON | Industry standard |
|
| 457 |
-
| Physics | Bar-and-hinge (NumPy) | Fast for RL, captures strain |
|
| 458 |
-
| Validation | Kawasaki + Maekawa + tri-tri intersection | Sound, polynomial time |
|
| 459 |
-
| Primary task | Solar panel packing | Unique, real-world, great demo |
|
| 460 |
-
| Training render | matplotlib headless | No GPU needed |
|
| 461 |
-
| Demo render | React + @react-three/fiber | Interactive, strain heatmap |
|
| 462 |
-
| Training | GRPO via TRL + Unsloth | Required by hackathon |
|
| 463 |
-
| Deployment | Docker Space on HF | Full control |
|
| 464 |
-
|
| 465 |
-
---
|
| 466 |
-
|
| 467 |
-
## 10. Why This Wins
|
| 468 |
-
|
| 469 |
-
1. **Unique** — nobody else doing origami RL
|
| 470 |
-
2. **Real-world** — NASA solar panels, medical stents, deployable shelters
|
| 471 |
-
3. **Demoable** — visual folding animation, strain heatmap, 1-min video
|
| 472 |
-
4. **Technically deep** — bar-and-hinge physics, Kawasaki/Maekawa math, material science
|
| 473 |
-
5. **Scalable** — FOLD format standard, material system, task curriculum
|
| 474 |
-
6. **Multi-statement** — Statement 2 (long-horizon planning) + Statement 3.1 (world modeling) + Statement 4 (self-improvement via curriculum)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/plan/openenv_arch.md
DELETED
|
@@ -1,1523 +0,0 @@
|
|
| 1 |
-
# OpenEnv Environment Architecture — Origami RL
|
| 2 |
-
|
| 3 |
-
> Complete blueprint. Everything lives inside the environment.
|
| 4 |
-
> Engine, physics, rendering, recording — all one deployable unit.
|
| 5 |
-
|
| 6 |
-
---
|
| 7 |
-
|
| 8 |
-
## 1. Overview
|
| 9 |
-
|
| 10 |
-
One Docker container on HF Spaces. Serves the OpenEnv API (WebSocket/REST) AND the React demo UI. Contains the full origami simulation engine, physics solver, validator, renderer, and metric system.
|
| 11 |
-
|
| 12 |
-
```
|
| 13 |
-
┌─────────────────────────────────────────────────────────┐
|
| 14 |
-
│ HF Space (Docker) │
|
| 15 |
-
│ │
|
| 16 |
-
│ ┌────────────────────────────────────────────────────┐ │
|
| 17 |
-
│ │ FastAPI (app.py) │ │
|
| 18 |
-
│ │ │ │
|
| 19 |
-
│ │ /ws, /reset, /step, /state → OpenEnv API │ │
|
| 20 |
-
│ │ / → React build │ │
|
| 21 |
-
│ │ /renders/* → Screenshots/GIFs │ │
|
| 22 |
-
│ │ /export/* → FOLD JSON export │ │
|
| 23 |
-
│ └─────────────────┬──────────────────────────────────┘ │
|
| 24 |
-
│ │ │
|
| 25 |
-
│ ┌─────────────────▼──────────────────────────────────┐ │
|
| 26 |
-
│ │ OrigamiEnvironment │ │
|
| 27 |
-
│ │ reset() / step() / state │ │
|
| 28 |
-
│ │ │ │
|
| 29 |
-
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
|
| 30 |
-
│ │ │ Engine │ │ Renderer │ │ Task System │ │ │
|
| 31 |
-
│ │ │ │ │ │ │ │ │ │
|
| 32 |
-
│ │ │ paper │ │ render2d │ │ task pool │ │ │
|
| 33 |
-
│ │ │ fold │ │ render3d │ │ materials │ │ │
|
| 34 |
-
│ │ │ physics │ │ capture │ │ curriculum │ │ │
|
| 35 |
-
│ │ │ validate │ │ record │ │ │ │ │
|
| 36 |
-
│ │ │ metrics │ │ export │ │ │ │ │
|
| 37 |
-
│ │ │ material │ │ │ │ │ │ │
|
| 38 |
-
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
|
| 39 |
-
│ └─────────────────────────────────────────────────────┘ │
|
| 40 |
-
│ │
|
| 41 |
-
│ ┌────────────────────────────────────────────────────┐ │
|
| 42 |
-
│ │ React Frontend (static build) │ │
|
| 43 |
-
│ │ CreasePattern(SVG) + FoldedView3D(R3F) + Metrics │ │
|
| 44 |
-
│ │ FoldAnimation + StrainHeatmap + MaterialSelector │ │
|
| 45 |
-
│ │ ScreenshotButton + RecordButton │ │
|
| 46 |
-
│ └────────────────────────────────────────────────────┘ │
|
| 47 |
-
└─────────────────────────────────────────────────────────┘
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
---
|
| 51 |
-
|
| 52 |
-
## 2. Repository Structure
|
| 53 |
-
|
| 54 |
-
```
|
| 55 |
-
origami_env/ # THE deliverable — one package
|
| 56 |
-
│
|
| 57 |
-
├── server/ # Python backend (everything)
|
| 58 |
-
│ │
|
| 59 |
-
│ ├── engine/ # Origami simulation core
|
| 60 |
-
│ │ ├── __init__.py
|
| 61 |
-
│ │ ├── paper.py # PaperState dataclass, FOLD I/O, create_flat_sheet()
|
| 62 |
-
│ │ ├── fold.py # apply_fold() — quaternion rotation, face splitting
|
| 63 |
-
│ │ ├── physics.py # Bar-and-hinge Verlet solver, strain computation
|
| 64 |
-
│ │ ├── validation.py # Kawasaki, Maekawa, self-intersection detection
|
| 65 |
-
│ │ ├── metrics.py # ALL metrics — compactness, strain, shape, deployability
|
| 66 |
-
│ │ └── materials.py # Material presets + stiffness parameter derivation
|
| 67 |
-
│ │
|
| 68 |
-
│ ├── renderer/ # All visualization
|
| 69 |
-
│ │ ├── __init__.py
|
| 70 |
-
│ │ ├── render_2d.py # matplotlib: crease pattern SVG/PNG (M=red, V=blue, B=black)
|
| 71 |
-
│ │ ├── render_3d.py # matplotlib: 3D wireframe + strain heatmap
|
| 72 |
-
│ │ ├── screenshots.py # Per-step PNG capture, episode summary grid
|
| 73 |
-
│ │ ├── recorder.py # GIF assembly from frames (imageio), fold animation
|
| 74 |
-
│ │ └── exporter.py # FOLD JSON export, OBJ/STL export for 3D printing
|
| 75 |
-
│ │
|
| 76 |
-
│ ├── models.py # Pydantic: OrigamiAction, OrigamiObservation, OrigamiState
|
| 77 |
-
│ ├── origami_environment.py # Environment class — reset/step/state (calls engine + renderer)
|
| 78 |
-
│ ├── tasks.py # Task pool, curriculum levels, difficulty sampling
|
| 79 |
-
│ ├── app.py # FastAPI: OpenEnv API + static React + render serving
|
| 80 |
-
│ ├── requirements.txt # numpy, scipy, matplotlib, imageio, pydantic, openenv-core
|
| 81 |
-
│ └── Dockerfile # Build React + run FastAPI
|
| 82 |
-
│
|
| 83 |
-
├── web/ # React frontend
|
| 84 |
-
│ ├── package.json
|
| 85 |
-
│ ├── tsconfig.json
|
| 86 |
-
│ ├── vite.config.ts
|
| 87 |
-
│ └── src/
|
| 88 |
-
│ ├── App.tsx # Main layout: 2D + 3D + metrics + controls
|
| 89 |
-
│ ├── components/
|
| 90 |
-
│ │ ├── CreasePattern.tsx # SVG: edges colored by M/V/B/F/U assignment
|
| 91 |
-
│ │ ├── FoldedView3D.tsx # R3F: BufferGeometry + OrbitControls + DoubleSide
|
| 92 |
-
│ │ ├── StrainHeatmap.tsx # Three.js Lut: vertex colors blue→red
|
| 93 |
-
│ │ ├── FoldAnimation.tsx # Timeline slider, play/pause, fold interpolation
|
| 94 |
-
│ │ ├── MetricsDashboard.tsx # Cards: all metrics with live updates
|
| 95 |
-
│ │ ├── MaterialSelector.tsx # Dropdown: paper/mylar/aluminum/nitinol
|
| 96 |
-
│ │ ├── TaskSelector.tsx # Pick task from curriculum
|
| 97 |
-
│ │ └── CaptureControls.tsx # Screenshot (canvas.toDataURL) + Record (MediaRecorder)
|
| 98 |
-
│ ├── hooks/
|
| 99 |
-
│ │ └── useEnvironment.ts # WebSocket connection to OpenEnv server
|
| 100 |
-
│ └── types.ts # TypeScript interfaces matching server models
|
| 101 |
-
│
|
| 102 |
-
├── client/ # OpenEnv client (for remote connection + training)
|
| 103 |
-
│ ├── __init__.py
|
| 104 |
-
│ ├── client.py # OrigamiEnvClient (EnvClient subclass)
|
| 105 |
-
│ └── reward_functions.py # code_valid, no_cheating, fold_quality
|
| 106 |
-
│
|
| 107 |
-
├── openenv.yaml # Manifest
|
| 108 |
-
├── pyproject.toml
|
| 109 |
-
└── README.md
|
| 110 |
-
```
|
| 111 |
-
|
| 112 |
-
---
|
| 113 |
-
|
| 114 |
-
## 3. Pydantic Models (`server/models.py`)
|
| 115 |
-
|
| 116 |
-
### OrigamiAction
|
| 117 |
-
|
| 118 |
-
One fold per step. Multi-step episodes (like 2048 where each step = one move).
|
| 119 |
-
|
| 120 |
-
```python
|
| 121 |
-
class OrigamiAction(Action):
|
| 122 |
-
"""One fold operation."""
|
| 123 |
-
# metadata: Dict[str, Any] (inherited)
|
| 124 |
-
|
| 125 |
-
fold_type: str # "valley" | "mountain" | "pleat" | "crimp" | "stop"
|
| 126 |
-
fold_line: Dict[str, List[float]] # {"start": [x,y], "end": [x,y]} (normalized 0-1)
|
| 127 |
-
fold_angle: float = 180.0 # degrees, 0-180 (180 = fully folded)
|
| 128 |
-
layer_select: str = "all" # "all" | "top" | "bottom"
|
| 129 |
-
```
|
| 130 |
-
|
| 131 |
-
`"stop"` action ends the episode and triggers final metrics + reward.
|
| 132 |
-
|
| 133 |
-
### OrigamiObservation
|
| 134 |
-
|
| 135 |
-
Everything the frontend AND the LLM need. Returned by both `reset()` and `step()`.
|
| 136 |
-
|
| 137 |
-
```python
|
| 138 |
-
class OrigamiObservation(Observation):
|
| 139 |
-
# done: bool (inherited)
|
| 140 |
-
# reward: float|None (inherited)
|
| 141 |
-
# metadata: Dict (inherited)
|
| 142 |
-
|
| 143 |
-
# ── Task ──────────────────────────────────────────
|
| 144 |
-
task: Dict[str, Any] = Field(default_factory=dict)
|
| 145 |
-
# {
|
| 146 |
-
# "name": "solar_panel",
|
| 147 |
-
# "description": "Pack a 1m x 1m Mylar solar panel...",
|
| 148 |
-
# "width": 1.0, "height": 1.0,
|
| 149 |
-
# "material": {"name": "mylar", "thickness_mm": 0.05, "youngs_modulus_gpa": 4.0, "max_strain": 0.03},
|
| 150 |
-
# "target_ratio": 0.05,
|
| 151 |
-
# "max_folds": 20,
|
| 152 |
-
# "target_box": [0.15, 0.15, 0.05],
|
| 153 |
-
# "must_deploy": True,
|
| 154 |
-
# "difficulty": 3,
|
| 155 |
-
# }
|
| 156 |
-
|
| 157 |
-
# ── Paper State (FOLD-compatible) ─────────────────
|
| 158 |
-
paper_state: Dict[str, Any] = Field(default_factory=dict)
|
| 159 |
-
# {
|
| 160 |
-
# "vertices_coords": [[x,y,z], ...], # (N,3) current 3D positions
|
| 161 |
-
# "edges_vertices": [[v1,v2], ...], # (E,2) edge connectivity
|
| 162 |
-
# "faces_vertices": [[v0,v1,v2,...], ...], # face polygons (CCW)
|
| 163 |
-
# "edges_assignment": ["M","V","B","F",...], # per-edge type
|
| 164 |
-
# "edges_foldAngle": [-180, 180, 0, ...], # per-edge target angle (degrees)
|
| 165 |
-
# "num_vertices": 25,
|
| 166 |
-
# "num_edges": 48,
|
| 167 |
-
# "num_faces": 24,
|
| 168 |
-
# "bounding_box": [0.15, 0.15, 0.05], # folded dimensions
|
| 169 |
-
# "num_layers": 8,
|
| 170 |
-
# "material": {"name": "mylar", ...},
|
| 171 |
-
#
|
| 172 |
-
# # Physics state
|
| 173 |
-
# "strain_per_vertex": [0.01, 0.005, ...], # per-vertex Cauchy strain
|
| 174 |
-
# "energy": {
|
| 175 |
-
# "total": 0.34,
|
| 176 |
-
# "bar": 0.12, # stretching energy
|
| 177 |
-
# "facet": 0.08, # panel bending energy
|
| 178 |
-
# "fold": 0.14, # crease folding energy
|
| 179 |
-
# },
|
| 180 |
-
# }
|
| 181 |
-
|
| 182 |
-
# ── Metrics ───────────────────────────────────────
|
| 183 |
-
metrics: Dict[str, Any] = Field(default_factory=dict)
|
| 184 |
-
# {
|
| 185 |
-
# ## Validity
|
| 186 |
-
# "is_valid": True,
|
| 187 |
-
# "kawasaki_violations": 0, # count of vertices violating Kawasaki
|
| 188 |
-
# "kawasaki_total_error": 0.0, # sum of |alt_angle_sum - 180| degrees
|
| 189 |
-
# "maekawa_violations": 0, # count of vertices violating |M-V|=2
|
| 190 |
-
# "self_intersections": 0, # count of face-face penetrations
|
| 191 |
-
# "strain_exceeded": False, # any vertex > material.max_strain?
|
| 192 |
-
#
|
| 193 |
-
# ## Compactness
|
| 194 |
-
# "deployment_ratio": 0.05, # folded_area / original_area
|
| 195 |
-
# "compactness": 0.95, # 1 - deployment_ratio
|
| 196 |
-
# "volume_compaction": 0.001, # bbox_folded / bbox_original
|
| 197 |
-
# "packing_efficiency": 0.72, # material_volume / bbox_volume
|
| 198 |
-
# "fits_target_box": True, # fits inside task.target_box?
|
| 199 |
-
#
|
| 200 |
-
# ## Structural
|
| 201 |
-
# "max_strain": 0.02, # max per-vertex strain
|
| 202 |
-
# "mean_strain": 0.008, # mean per-vertex strain
|
| 203 |
-
# "total_energy": 0.34, # total elastic energy
|
| 204 |
-
# "energy_bar": 0.12,
|
| 205 |
-
# "energy_facet": 0.08,
|
| 206 |
-
# "energy_fold": 0.14,
|
| 207 |
-
#
|
| 208 |
-
# ## Efficiency
|
| 209 |
-
# "fold_count": 8, # number of folds applied
|
| 210 |
-
# "folding_efficiency": 0.119, # compactness / fold_count
|
| 211 |
-
# "crease_complexity": 0.85, # entropy of M/V assignment distribution
|
| 212 |
-
#
|
| 213 |
-
# ## Deployability
|
| 214 |
-
# "is_deployable": True, # reverse fold simulation passed?
|
| 215 |
-
# "deployment_force_estimate": 0.45, # Newtons (from energy gradient)
|
| 216 |
-
#
|
| 217 |
-
# ## Shape similarity (if task has target_shape)
|
| 218 |
-
# "chamfer_distance": null, # avg nearest-point distance
|
| 219 |
-
# "hausdorff_distance": null, # max distance
|
| 220 |
-
# }
|
| 221 |
-
|
| 222 |
-
# ── Fold History ──────────────────────────────────
|
| 223 |
-
fold_history: List[Dict[str, Any]] = Field(default_factory=list)
|
| 224 |
-
# [ {"type":"valley", "line":{"start":[0,0.5],"end":[1,0.5]}, "angle":180, "step":1}, ... ]
|
| 225 |
-
|
| 226 |
-
# ── Error ─────────────────────────────────────────
|
| 227 |
-
error: Optional[str] = None
|
| 228 |
-
# "Fold line does not intersect paper boundary"
|
| 229 |
-
# "Self-intersection detected after fold 3"
|
| 230 |
-
# etc.
|
| 231 |
-
|
| 232 |
-
# ── Render URLs ───────────────────────────────────
|
| 233 |
-
render_urls: Dict[str, str] = Field(default_factory=dict)
|
| 234 |
-
# {
|
| 235 |
-
# "crease_2d": "/renders/ep_abc123/crease_step_4.png",
|
| 236 |
-
# "folded_3d": "/renders/ep_abc123/folded_step_4.png",
|
| 237 |
-
# "strain_heatmap": "/renders/ep_abc123/strain_step_4.png",
|
| 238 |
-
# "episode_gif": "/renders/ep_abc123/animation.gif", # only when done=True
|
| 239 |
-
# "fold_json": "/export/ep_abc123/state.fold", # FOLD format export
|
| 240 |
-
# }
|
| 241 |
-
```
|
| 242 |
-
|
| 243 |
-
### OrigamiState
|
| 244 |
-
|
| 245 |
-
Server-side episode tracking.
|
| 246 |
-
|
| 247 |
-
```python
|
| 248 |
-
class OrigamiState(State):
|
| 249 |
-
# episode_id: Optional[str] (inherited)
|
| 250 |
-
# step_count: int (inherited)
|
| 251 |
-
|
| 252 |
-
task_name: str = ""
|
| 253 |
-
num_folds_applied: int = 0
|
| 254 |
-
is_valid: bool = True
|
| 255 |
-
total_reward: float = 0.0
|
| 256 |
-
current_fold_percent: float = 1.0 # for animation: how far current fold has progressed
|
| 257 |
-
```
|
| 258 |
-
|
| 259 |
-
---
|
| 260 |
-
|
| 261 |
-
## 4. Engine (`server/engine/`)
|
| 262 |
-
|
| 263 |
-
### 4.1 Paper State (`paper.py`)
|
| 264 |
-
|
| 265 |
-
The core data structure. FOLD-format compatible.
|
| 266 |
-
|
| 267 |
-
```python
|
| 268 |
-
@dataclass
|
| 269 |
-
class PaperState:
|
| 270 |
-
# ── Geometry (FOLD format) ────────────────────────
|
| 271 |
-
vertices_coords: np.ndarray # (N, 3) vertex positions (3D)
|
| 272 |
-
edges_vertices: np.ndarray # (E, 2) edge connectivity, dtype int
|
| 273 |
-
faces_vertices: list[list[int]] # ragged: face polygons as vertex index lists (CCW)
|
| 274 |
-
edges_assignment: list[str] # (E,) "M" | "V" | "B" | "F" | "U" per edge
|
| 275 |
-
edges_foldAngle: np.ndarray # (E,) target fold angle in degrees per edge
|
| 276 |
-
|
| 277 |
-
# ── Physics ─────────────────────────���─────────────
|
| 278 |
-
rest_lengths: np.ndarray # (E,) original edge lengths (set at creation)
|
| 279 |
-
rest_positions: np.ndarray # (N, 3) original flat positions (for strain reference)
|
| 280 |
-
strain_per_vertex: np.ndarray # (N,) per-vertex Cauchy strain
|
| 281 |
-
energy: dict # {"total": float, "bar": float, "facet": float, "fold": float}
|
| 282 |
-
|
| 283 |
-
# ── Layers ────────────────────────────────────────
|
| 284 |
-
face_orders: list[tuple] # [(face_i, face_j, +1/-1), ...] layer ordering
|
| 285 |
-
num_layers: int # max stacking depth
|
| 286 |
-
|
| 287 |
-
# ── Material ──────────────────────────────────────
|
| 288 |
-
material: Material # thickness_mm, youngs_modulus_gpa, max_strain, poisson_ratio
|
| 289 |
-
|
| 290 |
-
# ── Metadata ──────────────────────────────────────
|
| 291 |
-
width: float # original sheet width (meters)
|
| 292 |
-
height: float # original sheet height (meters)
|
| 293 |
-
fold_count: int # number of folds applied so far
|
| 294 |
-
```
|
| 295 |
-
|
| 296 |
-
**Key methods:**
|
| 297 |
-
|
| 298 |
-
```python
|
| 299 |
-
create_flat_sheet(width, height, material) -> PaperState
|
| 300 |
-
# Creates a rectangular sheet: 4 vertices, 4 boundary edges, 1 face
|
| 301 |
-
# (or subdivided grid for higher resolution physics)
|
| 302 |
-
|
| 303 |
-
PaperState.to_fold_json() -> dict
|
| 304 |
-
# Exports FOLD-format JSON (vertices_coords, edges_vertices, edges_assignment, etc.)
|
| 305 |
-
|
| 306 |
-
PaperState.from_fold_json(data: dict) -> PaperState
|
| 307 |
-
# Imports from FOLD JSON
|
| 308 |
-
|
| 309 |
-
PaperState.to_observation_dict() -> dict
|
| 310 |
-
# Simplified dict for the Observation (includes strain, energy, bounding_box)
|
| 311 |
-
|
| 312 |
-
PaperState.bounding_box -> np.ndarray # (3,) min bounding box dimensions
|
| 313 |
-
|
| 314 |
-
PaperState.triangulated_faces -> list[list[int]]
|
| 315 |
-
# Ear-clipping triangulation of polygon faces (needed for physics + rendering)
|
| 316 |
-
```
|
| 317 |
-
|
| 318 |
-
### 4.2 Fold Operations (`fold.py`)
|
| 319 |
-
|
| 320 |
-
Applies one fold to the paper. Returns new PaperState.
|
| 321 |
-
|
| 322 |
-
```python
|
| 323 |
-
def apply_fold(paper: PaperState, fold: dict) -> PaperState:
|
| 324 |
-
"""
|
| 325 |
-
fold = {
|
| 326 |
-
"type": "valley" | "mountain" | "pleat" | "crimp",
|
| 327 |
-
"line": {"start": [x, y], "end": [x, y]},
|
| 328 |
-
"angle": 0-180,
|
| 329 |
-
"layer_select": "all" | "top" | "bottom",
|
| 330 |
-
}
|
| 331 |
-
"""
|
| 332 |
-
```
|
| 333 |
-
|
| 334 |
-
**The 10-step fold pipeline:**
|
| 335 |
-
|
| 336 |
-
```
|
| 337 |
-
Step 1: VALIDATE fold line
|
| 338 |
-
- Does line intersect the paper boundary at 2+ points?
|
| 339 |
-
- Is angle in valid range?
|
| 340 |
-
- Raise FoldError if invalid
|
| 341 |
-
|
| 342 |
-
Step 2: SPLIT FACES at fold line
|
| 343 |
-
- Find all faces intersected by the fold line
|
| 344 |
-
- Split each intersected face into sub-faces
|
| 345 |
-
- Add new vertices at intersection points
|
| 346 |
-
- Add new edges along the fold line
|
| 347 |
-
- Update faces_vertices, edges_vertices, edges_assignment
|
| 348 |
-
|
| 349 |
-
Step 3: CLASSIFY VERTICES
|
| 350 |
-
- For each vertex, determine which side of fold line it's on
|
| 351 |
-
- Use signed distance: d = (point - line_start) x line_direction
|
| 352 |
-
- Positive side = moving side, negative side = fixed side
|
| 353 |
-
- Vertices ON the line (|d| < epsilon) are hinge vertices
|
| 354 |
-
|
| 355 |
-
Step 4: APPLY ROTATION (quaternion)
|
| 356 |
-
- Rotation axis = fold line direction vector (normalized)
|
| 357 |
-
- Rotation angle = fold_angle (valley=positive, mountain=negative)
|
| 358 |
-
- For each moving vertex:
|
| 359 |
-
translate to fold line origin → apply quaternion → translate back
|
| 360 |
-
- Quaternion rotation for numerical stability (no gimbal lock)
|
| 361 |
-
|
| 362 |
-
Step 5: UPDATE EDGE ASSIGNMENTS
|
| 363 |
-
- New edges along fold line: "M" or "V" based on fold_type
|
| 364 |
-
- Valley fold → "V" (positive fold angle, paper toward you)
|
| 365 |
-
- Mountain fold → "M" (negative fold angle, paper away)
|
| 366 |
-
|
| 367 |
-
Step 6: UPDATE FOLD ANGLES
|
| 368 |
-
- New crease edges: set target angle (±180 for full fold, ±fold_angle otherwise)
|
| 369 |
-
- Existing assignments unchanged
|
| 370 |
-
|
| 371 |
-
Step 7: UPDATE FACE TOPOLOGY
|
| 372 |
-
- Recompute faces_vertices after split
|
| 373 |
-
- Recompute face_orders (layer ordering) based on rotation
|
| 374 |
-
|
| 375 |
-
Step 8: COMPUTE REST LENGTHS for new edges
|
| 376 |
-
- rest_length = euclidean distance in the FLAT (unfolded) configuration
|
| 377 |
-
- This is the reference for strain computation
|
| 378 |
-
|
| 379 |
-
Step 9: INCREMENT fold_count
|
| 380 |
-
|
| 381 |
-
Step 10: RETURN new PaperState
|
| 382 |
-
- Physics and validation run SEPARATELY (called by environment)
|
| 383 |
-
- This keeps fold.py focused on geometry only
|
| 384 |
-
```
|
| 385 |
-
|
| 386 |
-
**Pleat and crimp are compound folds:**
|
| 387 |
-
|
| 388 |
-
```python
|
| 389 |
-
def apply_pleat(paper, line1, line2, angle):
|
| 390 |
-
"""Two parallel folds: valley at line1, mountain at line2."""
|
| 391 |
-
paper = apply_fold(paper, {"type": "valley", "line": line1, "angle": angle})
|
| 392 |
-
paper = apply_fold(paper, {"type": "mountain", "line": line2, "angle": angle})
|
| 393 |
-
return paper
|
| 394 |
-
|
| 395 |
-
def apply_crimp(paper, line1, line2, angle):
|
| 396 |
-
"""Two parallel folds: mountain at line1, valley at line2 (reverse of pleat)."""
|
| 397 |
-
paper = apply_fold(paper, {"type": "mountain", "line": line1, "angle": angle})
|
| 398 |
-
paper = apply_fold(paper, {"type": "valley", "line": line2, "angle": angle})
|
| 399 |
-
return paper
|
| 400 |
-
```
|
| 401 |
-
|
| 402 |
-
### 4.3 Physics Solver (`physics.py`)
|
| 403 |
-
|
| 404 |
-
Bar-and-hinge model. NumPy port of Ghassaei's GPU solver.
|
| 405 |
-
|
| 406 |
-
**Three constraint types (energy components):**
|
| 407 |
-
|
| 408 |
-
```python
|
| 409 |
-
# E_total = E_bar + E_facet + E_fold
|
| 410 |
-
|
| 411 |
-
# 1. BAR (axial spring) — every edge resists stretching/compression
|
| 412 |
-
# E_bar = Σ (1/2) * k_axial * (L - L0)²
|
| 413 |
-
# k_axial = E * t * w / L0
|
| 414 |
-
# Where: E = Young's modulus, t = thickness, w = tributary width, L0 = rest length
|
| 415 |
-
|
| 416 |
-
# 2. FACET HINGE — triangulation diagonals keep faces flat
|
| 417 |
-
# E_facet = Σ (1/2) * k_facet * l * (θ - π)²
|
| 418 |
-
# k_facet = E * t³ / (12 * (1 - ν²))
|
| 419 |
-
# Target angle = π (flat), high stiffness
|
| 420 |
-
# l = hinge edge length
|
| 421 |
-
|
| 422 |
-
# 3. FOLD HINGE — crease edges drive toward target fold angle
|
| 423 |
-
# E_fold = Σ (1/2) * k_fold * l * (ρ - ρ_target)²
|
| 424 |
-
# k_fold = κ (crease torsional stiffness, user-adjustable)
|
| 425 |
-
# ρ_target from edges_foldAngle * fold_percent
|
| 426 |
-
```
|
| 427 |
-
|
| 428 |
-
**Stiffness hierarchy:**
|
| 429 |
-
|
| 430 |
-
```python
|
| 431 |
-
# Prevents stretching while allowing controlled folding:
|
| 432 |
-
k_axial = 70.0 # very stiff — bars don't stretch
|
| 433 |
-
k_facet = 0.2 # moderate — faces stay flat but can flex slightly
|
| 434 |
-
k_fold = 0.7 # soft — drives folding motion
|
| 435 |
-
```
|
| 436 |
-
|
| 437 |
-
**Verlet integration solver:**
|
| 438 |
-
|
| 439 |
-
```python
|
| 440 |
-
def simulate(paper: PaperState, fold_percent: float = 1.0, n_steps: int = 500,
|
| 441 |
-
dt: float = 0.02, damping: float = 0.1) -> PaperState:
|
| 442 |
-
"""
|
| 443 |
-
Run bar-and-hinge physics simulation.
|
| 444 |
-
Updates vertex positions to satisfy fold constraints while minimizing energy.
|
| 445 |
-
Computes strain per vertex and total energy.
|
| 446 |
-
"""
|
| 447 |
-
pos = paper.vertices_coords.copy() # (N, 3) current positions
|
| 448 |
-
last_pos = pos.copy() # (N, 3) previous positions
|
| 449 |
-
|
| 450 |
-
# Precompute topology
|
| 451 |
-
beams = build_beam_list(paper) # [(node_a, node_b, rest_len, k), ...]
|
| 452 |
-
creases = build_crease_list(paper) # [(n1, n2, n3, n4, target_angle, k, type), ...]
|
| 453 |
-
|
| 454 |
-
for step in range(n_steps):
|
| 455 |
-
forces = np.zeros_like(pos) # (N, 3)
|
| 456 |
-
|
| 457 |
-
# ── Beam forces (vectorized) ─────────────────
|
| 458 |
-
for (a, b, L0, k) in beams:
|
| 459 |
-
delta = pos[b] - pos[a]
|
| 460 |
-
L = np.linalg.norm(delta)
|
| 461 |
-
if L < 1e-12: continue
|
| 462 |
-
strain = (L - L0) / L0
|
| 463 |
-
F_mag = k * strain * L0
|
| 464 |
-
F_dir = delta / L
|
| 465 |
-
forces[a] += F_mag * F_dir
|
| 466 |
-
forces[b] -= F_mag * F_dir
|
| 467 |
-
|
| 468 |
-
# ── Crease forces (dihedral angle springs) ───
|
| 469 |
-
for (n1, n2, n3, n4, target, k, ctype) in creases:
|
| 470 |
-
actual_target = target * fold_percent if ctype == "fold" else target
|
| 471 |
-
theta = compute_dihedral_angle(pos[n1], pos[n2], pos[n3], pos[n4])
|
| 472 |
-
delta_theta = theta - actual_target
|
| 473 |
-
torque = k * edge_length(pos[n1], pos[n2]) * delta_theta
|
| 474 |
-
|
| 475 |
-
# Convert torque to forces on the 4 nodes
|
| 476 |
-
f3, f4, f1, f2 = torque_to_forces(
|
| 477 |
-
pos[n1], pos[n2], pos[n3], pos[n4], torque
|
| 478 |
-
)
|
| 479 |
-
forces[n1] += f1
|
| 480 |
-
forces[n2] += f2
|
| 481 |
-
forces[n3] += f3
|
| 482 |
-
forces[n4] += f4
|
| 483 |
-
|
| 484 |
-
# ── Verlet integration ───────────────────────
|
| 485 |
-
new_pos = pos + (1.0 - damping) * (pos - last_pos) + forces * dt * dt
|
| 486 |
-
last_pos = pos
|
| 487 |
-
pos = new_pos
|
| 488 |
-
|
| 489 |
-
# ── Convergence check ────────────────────────
|
| 490 |
-
kinetic_energy = np.sum((pos - last_pos) ** 2)
|
| 491 |
-
if kinetic_energy < 1e-10:
|
| 492 |
-
break
|
| 493 |
-
|
| 494 |
-
# ── Compute strain ───────────────────────────────
|
| 495 |
-
paper.vertices_coords = pos
|
| 496 |
-
paper.strain_per_vertex = compute_strain(pos, paper.edges_vertices, paper.rest_lengths)
|
| 497 |
-
|
| 498 |
-
# ── Compute energy breakdown ─────────────────────
|
| 499 |
-
paper.energy = {
|
| 500 |
-
"total": compute_total_energy(pos, beams, creases, fold_percent),
|
| 501 |
-
"bar": compute_bar_energy(pos, beams),
|
| 502 |
-
"facet": compute_facet_energy(pos, creases, fold_percent),
|
| 503 |
-
"fold": compute_fold_energy(pos, creases, fold_percent),
|
| 504 |
-
}
|
| 505 |
-
|
| 506 |
-
return paper
|
| 507 |
-
```
|
| 508 |
-
|
| 509 |
-
**Strain computation (Ghassaei's formula):**
|
| 510 |
-
|
| 511 |
-
```python
|
| 512 |
-
def compute_strain(vertices, edges, rest_lengths) -> np.ndarray:
|
| 513 |
-
"""Per-vertex Cauchy strain = average percent deviation of incident edge lengths."""
|
| 514 |
-
strain = np.zeros(len(vertices))
|
| 515 |
-
counts = np.zeros(len(vertices))
|
| 516 |
-
|
| 517 |
-
for e_idx, (v1, v2) in enumerate(edges):
|
| 518 |
-
L = np.linalg.norm(vertices[v1] - vertices[v2])
|
| 519 |
-
L0 = rest_lengths[e_idx]
|
| 520 |
-
edge_strain = abs(L - L0) / L0
|
| 521 |
-
|
| 522 |
-
strain[v1] += edge_strain
|
| 523 |
-
strain[v2] += edge_strain
|
| 524 |
-
counts[v1] += 1
|
| 525 |
-
counts[v2] += 1
|
| 526 |
-
|
| 527 |
-
counts[counts == 0] = 1 # avoid division by zero
|
| 528 |
-
return strain / counts
|
| 529 |
-
```
|
| 530 |
-
|
| 531 |
-
**Dihedral angle computation:**
|
| 532 |
-
|
| 533 |
-
```python
|
| 534 |
-
def compute_dihedral_angle(p1, p2, p3, p4) -> float:
|
| 535 |
-
"""
|
| 536 |
-
Dihedral angle between planes (p1,p2,p3) and (p1,p2,p4).
|
| 537 |
-
p1-p2 is the hinge edge. p3 and p4 are wing tips.
|
| 538 |
-
|
| 539 |
-
p3
|
| 540 |
-
/ | \
|
| 541 |
-
/ | \
|
| 542 |
-
p1----+----p2 (hinge edge)
|
| 543 |
-
\ | /
|
| 544 |
-
\ | /
|
| 545 |
-
p4
|
| 546 |
-
|
| 547 |
-
Returns angle in radians. 0 = flat, π = folded 180°.
|
| 548 |
-
"""
|
| 549 |
-
e = p2 - p1 # hinge edge vector
|
| 550 |
-
n1 = np.cross(p3 - p1, e) # normal of face 1
|
| 551 |
-
n2 = np.cross(e, p4 - p1) # normal of face 2
|
| 552 |
-
n1 = n1 / np.linalg.norm(n1)
|
| 553 |
-
n2 = n2 / np.linalg.norm(n2)
|
| 554 |
-
|
| 555 |
-
cos_theta = np.clip(np.dot(n1, n2), -1, 1)
|
| 556 |
-
sin_theta = np.dot(np.cross(n1, n2), e / np.linalg.norm(e))
|
| 557 |
-
return np.arctan2(sin_theta, cos_theta)
|
| 558 |
-
```
|
| 559 |
-
|
| 560 |
-
### 4.4 Validation (`validation.py`)
|
| 561 |
-
|
| 562 |
-
```python
|
| 563 |
-
def validate_state(paper: PaperState) -> dict:
|
| 564 |
-
"""Run all validation checks. Returns violation report."""
|
| 565 |
-
return {
|
| 566 |
-
# ── Kawasaki-Justin theorem ───────────────────
|
| 567 |
-
# At each interior vertex: alternating angle sum = 180°
|
| 568 |
-
"kawasaki_violations": count_kawasaki_violations(paper),
|
| 569 |
-
"kawasaki_total_error": sum_kawasaki_error(paper), # degrees
|
| 570 |
-
|
| 571 |
-
# ── Maekawa-Justin theorem ────────────────────
|
| 572 |
-
# At each interior vertex: |M - V| = 2
|
| 573 |
-
"maekawa_violations": count_maekawa_violations(paper),
|
| 574 |
-
|
| 575 |
-
# ── Self-intersection ─────────────────────────
|
| 576 |
-
# Triangle-triangle intersection test on all non-adjacent face pairs
|
| 577 |
-
"self_intersections": count_self_intersections(paper),
|
| 578 |
-
|
| 579 |
-
# ── Material limits ───────────────────────────
|
| 580 |
-
# Is max strain within material tolerance?
|
| 581 |
-
"strain_exceeded": bool(np.max(paper.strain_per_vertex) > paper.material.max_strain),
|
| 582 |
-
"max_strain_ratio": float(np.max(paper.strain_per_vertex) / paper.material.max_strain),
|
| 583 |
-
|
| 584 |
-
# ── Summary ───────────────────────────────────
|
| 585 |
-
"is_valid": (
|
| 586 |
-
count_kawasaki_violations(paper) == 0 and
|
| 587 |
-
count_maekawa_violations(paper) == 0 and
|
| 588 |
-
count_self_intersections(paper) == 0 and
|
| 589 |
-
np.max(paper.strain_per_vertex) <= paper.material.max_strain
|
| 590 |
-
),
|
| 591 |
-
}
|
| 592 |
-
```
|
| 593 |
-
|
| 594 |
-
**Kawasaki check:**
|
| 595 |
-
|
| 596 |
-
```python
|
| 597 |
-
def check_kawasaki_at_vertex(paper, vertex_idx) -> float:
|
| 598 |
-
"""Returns angular error in degrees. 0 = valid."""
|
| 599 |
-
# Get all crease edges incident on this vertex, sorted by angle
|
| 600 |
-
angles = get_sorted_crease_angles(paper, vertex_idx)
|
| 601 |
-
if len(angles) < 2:
|
| 602 |
-
return 0.0
|
| 603 |
-
# Alternating sum: a1 - a2 + a3 - a4 + ... should = 0
|
| 604 |
-
alternating = sum(a * (-1)**i for i, a in enumerate(angles))
|
| 605 |
-
return abs(alternating)
|
| 606 |
-
```
|
| 607 |
-
|
| 608 |
-
**Self-intersection detection:**
|
| 609 |
-
|
| 610 |
-
```python
|
| 611 |
-
def count_self_intersections(paper) -> int:
|
| 612 |
-
"""Triangle-triangle intersection test. O(F²) but F is small for our models."""
|
| 613 |
-
triangles = paper.triangulated_faces
|
| 614 |
-
count = 0
|
| 615 |
-
for i in range(len(triangles)):
|
| 616 |
-
for j in range(i + 2, len(triangles)): # skip adjacent faces
|
| 617 |
-
if not faces_share_edge(triangles[i], triangles[j]):
|
| 618 |
-
if triangles_intersect(
|
| 619 |
-
paper.vertices_coords[triangles[i]],
|
| 620 |
-
paper.vertices_coords[triangles[j]]
|
| 621 |
-
):
|
| 622 |
-
count += 1
|
| 623 |
-
return count
|
| 624 |
-
```
|
| 625 |
-
|
| 626 |
-
### 4.5 Metrics (`metrics.py`)
|
| 627 |
-
|
| 628 |
-
All metrics computed from PaperState + task. Returns flat dict for the Observation.
|
| 629 |
-
|
| 630 |
-
```python
|
| 631 |
-
def compute_all_metrics(paper: PaperState, task: dict, validation: dict) -> dict:
|
| 632 |
-
"""Compute every metric. Called after physics + validation."""
|
| 633 |
-
|
| 634 |
-
original_area = paper.width * paper.height
|
| 635 |
-
bb = paper.bounding_box # (3,) array
|
| 636 |
-
original_bbox_vol = paper.width * paper.height * paper.material.thickness_mm / 1000
|
| 637 |
-
folded_bbox_vol = bb[0] * bb[1] * bb[2]
|
| 638 |
-
|
| 639 |
-
return {
|
| 640 |
-
# ── Validity (from validation) ────────────────
|
| 641 |
-
"is_valid": validation["is_valid"],
|
| 642 |
-
"kawasaki_violations": validation["kawasaki_violations"],
|
| 643 |
-
"kawasaki_total_error": validation["kawasaki_total_error"],
|
| 644 |
-
"maekawa_violations": validation["maekawa_violations"],
|
| 645 |
-
"self_intersections": validation["self_intersections"],
|
| 646 |
-
"strain_exceeded": validation["strain_exceeded"],
|
| 647 |
-
|
| 648 |
-
# ── Compactness ──────────────────────────────
|
| 649 |
-
"deployment_ratio": folded_area(paper) / original_area,
|
| 650 |
-
"compactness": 1.0 - (folded_area(paper) / original_area),
|
| 651 |
-
"volume_compaction": folded_bbox_vol / original_bbox_vol if original_bbox_vol > 0 else 0,
|
| 652 |
-
"packing_efficiency": material_volume(paper) / folded_bbox_vol if folded_bbox_vol > 0 else 0,
|
| 653 |
-
"fits_target_box": fits_in_box(bb, task.get("target_box")),
|
| 654 |
-
"bounding_box": bb.tolist(),
|
| 655 |
-
|
| 656 |
-
# ── Structural ───────────────────────────────
|
| 657 |
-
"max_strain": float(np.max(paper.strain_per_vertex)),
|
| 658 |
-
"mean_strain": float(np.mean(paper.strain_per_vertex)),
|
| 659 |
-
"total_energy": paper.energy["total"],
|
| 660 |
-
"energy_bar": paper.energy["bar"],
|
| 661 |
-
"energy_facet": paper.energy["facet"],
|
| 662 |
-
"energy_fold": paper.energy["fold"],
|
| 663 |
-
|
| 664 |
-
# ── Efficiency ───────────────────────────────
|
| 665 |
-
"fold_count": paper.fold_count,
|
| 666 |
-
"folding_efficiency": (1.0 - folded_area(paper) / original_area) / max(paper.fold_count, 1),
|
| 667 |
-
"crease_complexity": assignment_entropy(paper.edges_assignment),
|
| 668 |
-
|
| 669 |
-
# ── Deployability ────────────────────────────
|
| 670 |
-
"is_deployable": check_deployability(paper) if task.get("must_deploy") else None,
|
| 671 |
-
"deployment_force_estimate": estimate_deployment_force(paper),
|
| 672 |
-
|
| 673 |
-
# ── Shape similarity (if target given) ───────
|
| 674 |
-
"chamfer_distance": (
|
| 675 |
-
compute_chamfer_distance(paper, task["target_shape"])
|
| 676 |
-
if "target_shape" in task else None
|
| 677 |
-
),
|
| 678 |
-
"hausdorff_distance": (
|
| 679 |
-
compute_hausdorff_distance(paper, task["target_shape"])
|
| 680 |
-
if "target_shape" in task else None
|
| 681 |
-
),
|
| 682 |
-
}
|
| 683 |
-
```
|
| 684 |
-
|
| 685 |
-
### 4.6 Materials (`materials.py`)
|
| 686 |
-
|
| 687 |
-
```python
|
| 688 |
-
@dataclass
|
| 689 |
-
class Material:
|
| 690 |
-
name: str
|
| 691 |
-
thickness_mm: float # mm
|
| 692 |
-
youngs_modulus_gpa: float # GPa
|
| 693 |
-
max_strain: float # fraction (0.05 = 5%)
|
| 694 |
-
poisson_ratio: float = 0.3
|
| 695 |
-
density_kg_m3: float = 1000.0
|
| 696 |
-
|
| 697 |
-
# Derived stiffness parameters (for physics solver)
|
| 698 |
-
@property
|
| 699 |
-
def k_axial(self) -> float:
|
| 700 |
-
"""Axial stiffness coefficient."""
|
| 701 |
-
return self.youngs_modulus_gpa * 1e9 * (self.thickness_mm / 1000)
|
| 702 |
-
|
| 703 |
-
@property
|
| 704 |
-
def k_facet(self) -> float:
|
| 705 |
-
"""Facet bending stiffness."""
|
| 706 |
-
t = self.thickness_mm / 1000
|
| 707 |
-
E = self.youngs_modulus_gpa * 1e9
|
| 708 |
-
nu = self.poisson_ratio
|
| 709 |
-
return E * t**3 / (12 * (1 - nu**2))
|
| 710 |
-
|
| 711 |
-
MATERIALS = {
|
| 712 |
-
"paper": Material(
|
| 713 |
-
name="paper",
|
| 714 |
-
thickness_mm=0.1,
|
| 715 |
-
youngs_modulus_gpa=3.0,
|
| 716 |
-
max_strain=0.05, # 5% — paper is forgiving
|
| 717 |
-
poisson_ratio=0.3,
|
| 718 |
-
density_kg_m3=700,
|
| 719 |
-
),
|
| 720 |
-
"mylar": Material(
|
| 721 |
-
name="mylar",
|
| 722 |
-
thickness_mm=0.05,
|
| 723 |
-
youngs_modulus_gpa=4.0,
|
| 724 |
-
max_strain=0.03, # 3% — space-grade film
|
| 725 |
-
poisson_ratio=0.38,
|
| 726 |
-
density_kg_m3=1390,
|
| 727 |
-
),
|
| 728 |
-
"aluminum": Material(
|
| 729 |
-
name="aluminum",
|
| 730 |
-
thickness_mm=0.2,
|
| 731 |
-
youngs_modulus_gpa=70.0,
|
| 732 |
-
max_strain=0.01, # 1% — rigid, cracks easily at creases
|
| 733 |
-
poisson_ratio=0.33,
|
| 734 |
-
density_kg_m3=2700,
|
| 735 |
-
),
|
| 736 |
-
"nitinol": Material(
|
| 737 |
-
name="nitinol",
|
| 738 |
-
thickness_mm=0.15,
|
| 739 |
-
youngs_modulus_gpa=75.0,
|
| 740 |
-
max_strain=0.08, # 8% — superelastic shape memory alloy
|
| 741 |
-
poisson_ratio=0.33,
|
| 742 |
-
density_kg_m3=6450,
|
| 743 |
-
),
|
| 744 |
-
}
|
| 745 |
-
```
|
| 746 |
-
|
| 747 |
-
---
|
| 748 |
-
|
| 749 |
-
## 5. Renderer (`server/renderer/`)
|
| 750 |
-
|
| 751 |
-
### 5.1 2D Crease Pattern (`render_2d.py`)
|
| 752 |
-
|
| 753 |
-
```python
|
| 754 |
-
def render_crease_pattern(paper: PaperState, output_path: str = None) -> Image:
|
| 755 |
-
"""
|
| 756 |
-
matplotlib: crease pattern with standard origami colors.
|
| 757 |
-
|
| 758 |
-
Edge colors:
|
| 759 |
-
M (mountain) = red, dashed (dash-dot-dot)
|
| 760 |
-
V (valley) = blue, dash-dot
|
| 761 |
-
B (boundary) = black, solid
|
| 762 |
-
F (flat) = lightgray, solid thin
|
| 763 |
-
U (unassigned) = gray, dotted
|
| 764 |
-
|
| 765 |
-
Also renders:
|
| 766 |
-
- Vertex dots (gray)
|
| 767 |
-
- Fold angle labels (optional)
|
| 768 |
-
- Sheet dimensions annotation
|
| 769 |
-
"""
|
| 770 |
-
|
| 771 |
-
def render_crease_pattern_svg(paper: PaperState) -> str:
|
| 772 |
-
"""Returns inline SVG string (for React frontend fallback)."""
|
| 773 |
-
```
|
| 774 |
-
|
| 775 |
-
### 5.2 3D Folded View (`render_3d.py`)
|
| 776 |
-
|
| 777 |
-
```python
|
| 778 |
-
def render_folded_state(paper: PaperState, output_path: str = None,
|
| 779 |
-
view_angle: tuple = (30, 45)) -> Image:
|
| 780 |
-
"""
|
| 781 |
-
matplotlib mplot3d: wireframe + face shading with strain colors.
|
| 782 |
-
|
| 783 |
-
- Faces colored by strain: blue (0) → yellow → red (max)
|
| 784 |
-
- Edges drawn: M=red, V=blue, B=black
|
| 785 |
-
- Colorbar showing strain scale
|
| 786 |
-
- Bounding box wireframe overlay
|
| 787 |
-
"""
|
| 788 |
-
|
| 789 |
-
def render_strain_heatmap(paper: PaperState, output_path: str = None) -> Image:
|
| 790 |
-
"""
|
| 791 |
-
Top-down view of strain distribution.
|
| 792 |
-
Uses matplotlib tricontourf for smooth interpolation.
|
| 793 |
-
Colorbar: blue (0 strain) → red (max strain).
|
| 794 |
-
Material limit marked as dashed line on colorbar.
|
| 795 |
-
"""
|
| 796 |
-
|
| 797 |
-
def render_side_by_side(paper: PaperState, output_path: str = None) -> Image:
|
| 798 |
-
"""
|
| 799 |
-
Combined figure:
|
| 800 |
-
Left: 2D crease pattern
|
| 801 |
-
Right: 3D folded state with strain colors
|
| 802 |
-
Bottom: metrics text summary
|
| 803 |
-
"""
|
| 804 |
-
```
|
| 805 |
-
|
| 806 |
-
### 5.3 Screenshots (`screenshots.py`)
|
| 807 |
-
|
| 808 |
-
```python
|
| 809 |
-
def capture_step(paper: PaperState, step_num: int,
|
| 810 |
-
episode_dir: str) -> dict:
|
| 811 |
-
"""
|
| 812 |
-
Save renders for one step. Returns dict of file paths.
|
| 813 |
-
Creates:
|
| 814 |
-
- {episode_dir}/crease_step_{N}.png (2D crease pattern)
|
| 815 |
-
- {episode_dir}/folded_step_{N}.png (3D folded state)
|
| 816 |
-
- {episode_dir}/strain_step_{N}.png (strain heatmap)
|
| 817 |
-
- {episode_dir}/state_step_{N}.fold (FOLD JSON snapshot)
|
| 818 |
-
"""
|
| 819 |
-
|
| 820 |
-
def capture_episode_summary(paper: PaperState, fold_history: list,
|
| 821 |
-
task: dict, metrics: dict,
|
| 822 |
-
episode_dir: str) -> str:
|
| 823 |
-
"""
|
| 824 |
-
Grid summary of entire episode. Returns path.
|
| 825 |
-
Creates: {episode_dir}/summary.png
|
| 826 |
-
|
| 827 |
-
Layout:
|
| 828 |
-
┌──────┬──────┬──────┬──────┐
|
| 829 |
-
│Step 0│Step 1│Step 2│Step 3│ (crease patterns)
|
| 830 |
-
├──────┼──────┼──────┼──────┤
|
| 831 |
-
│Step 0│Step 1│Step 2│Step 3│ (3D folded states)
|
| 832 |
-
├──────┴──────┴──────┴──────┤
|
| 833 |
-
│ Final metrics + task │
|
| 834 |
-
└───────────────────────────┘
|
| 835 |
-
"""
|
| 836 |
-
```
|
| 837 |
-
|
| 838 |
-
### 5.4 Recording (`recorder.py`)
|
| 839 |
-
|
| 840 |
-
```python
|
| 841 |
-
def record_fold_animation(paper_initial: PaperState, fold_history: list,
|
| 842 |
-
output_path: str, fps: int = 15,
|
| 843 |
-
frames_per_fold: int = 10) -> str:
|
| 844 |
-
"""
|
| 845 |
-
Generate animated GIF of the folding sequence.
|
| 846 |
-
|
| 847 |
-
For each fold in history:
|
| 848 |
-
- Interpolate fold_percent from 0.0 to 1.0 over frames_per_fold frames
|
| 849 |
-
- Run physics at each fold_percent
|
| 850 |
-
- Render 3D frame via matplotlib
|
| 851 |
-
Assemble frames into GIF via imageio.
|
| 852 |
-
|
| 853 |
-
Returns path to GIF.
|
| 854 |
-
"""
|
| 855 |
-
|
| 856 |
-
def record_strain_evolution(paper_initial: PaperState, fold_history: list,
|
| 857 |
-
output_path: str) -> str:
|
| 858 |
-
"""
|
| 859 |
-
GIF showing how strain develops through the fold sequence.
|
| 860 |
-
Useful for understanding which folds cause the most stress.
|
| 861 |
-
"""
|
| 862 |
-
```
|
| 863 |
-
|
| 864 |
-
### 5.5 Exporter (`exporter.py`)
|
| 865 |
-
|
| 866 |
-
```python
|
| 867 |
-
def export_fold_json(paper: PaperState, fold_history: list) -> dict:
|
| 868 |
-
"""
|
| 869 |
-
Full FOLD JSON with multi-frame animation data.
|
| 870 |
-
Can be loaded by OrigamiSimulator or our React frontend.
|
| 871 |
-
|
| 872 |
-
Includes:
|
| 873 |
-
- file_spec, file_creator, file_classes
|
| 874 |
-
- frame_classes: ["creasePattern"]
|
| 875 |
-
- All vertex/edge/face data
|
| 876 |
-
- fold_history as custom metadata
|
| 877 |
-
"""
|
| 878 |
-
|
| 879 |
-
def export_obj(paper: PaperState) -> str:
|
| 880 |
-
"""Wavefront OBJ format for 3D printing / external renderers."""
|
| 881 |
-
|
| 882 |
-
def export_stl(paper: PaperState) -> bytes:
|
| 883 |
-
"""STL binary format for 3D printing."""
|
| 884 |
-
```
|
| 885 |
-
|
| 886 |
-
---
|
| 887 |
-
|
| 888 |
-
## 6. Environment (`server/origami_environment.py`)
|
| 889 |
-
|
| 890 |
-
The OpenEnv wrapper. Calls engine + renderer. Does NOT contain origami logic.
|
| 891 |
-
|
| 892 |
-
```python
|
| 893 |
-
class OrigamiEnvironment(Environment[OrigamiAction, OrigamiObservation, OrigamiState]):
|
| 894 |
-
SUPPORTS_CONCURRENT_SESSIONS = False
|
| 895 |
-
|
| 896 |
-
def __init__(self):
|
| 897 |
-
self._paper = None
|
| 898 |
-
self._task = None
|
| 899 |
-
self._fold_history = []
|
| 900 |
-
self._metrics = {}
|
| 901 |
-
self._validation = {}
|
| 902 |
-
self._error = None
|
| 903 |
-
self._episode_id = None
|
| 904 |
-
self._step_count = 0
|
| 905 |
-
self._episode_dir = None # for renders
|
| 906 |
-
|
| 907 |
-
# ── reset ─────────────────────────────────────────
|
| 908 |
-
|
| 909 |
-
def reset(self, seed=None, episode_id=None, **kwargs) -> OrigamiObservation:
|
| 910 |
-
self._episode_id = episode_id or str(uuid.uuid4())
|
| 911 |
-
self._step_count = 0
|
| 912 |
-
self._fold_history = []
|
| 913 |
-
self._error = None
|
| 914 |
-
|
| 915 |
-
# Create episode render directory
|
| 916 |
-
self._episode_dir = f"renders/ep_{self._episode_id[:8]}"
|
| 917 |
-
os.makedirs(self._episode_dir, exist_ok=True)
|
| 918 |
-
|
| 919 |
-
# Sample task
|
| 920 |
-
self._task = kwargs.get("task") or sample_task(seed=seed)
|
| 921 |
-
|
| 922 |
-
# Create flat sheet
|
| 923 |
-
self._paper = create_flat_sheet(
|
| 924 |
-
self._task["width"], self._task["height"],
|
| 925 |
-
MATERIALS[self._task["material"]] if isinstance(self._task["material"], str)
|
| 926 |
-
else self._task["material"]
|
| 927 |
-
)
|
| 928 |
-
|
| 929 |
-
# Initial metrics + validation
|
| 930 |
-
self._validation = validate_state(self._paper)
|
| 931 |
-
self._metrics = compute_all_metrics(self._paper, self._task, self._validation)
|
| 932 |
-
|
| 933 |
-
# Render initial state
|
| 934 |
-
render_urls = capture_step(self._paper, 0, self._episode_dir)
|
| 935 |
-
|
| 936 |
-
return self._make_observation(done=False, reward=None, render_urls=render_urls)
|
| 937 |
-
|
| 938 |
-
# ── step ──────────────────────────────────────────
|
| 939 |
-
|
| 940 |
-
def step(self, action: OrigamiAction, timeout_s=None, **kwargs) -> OrigamiObservation:
|
| 941 |
-
self._step_count += 1
|
| 942 |
-
self._error = None
|
| 943 |
-
|
| 944 |
-
# ── Handle "stop" action ──────────────────────
|
| 945 |
-
if action.fold_type == "stop":
|
| 946 |
-
return self._finalize_episode()
|
| 947 |
-
|
| 948 |
-
# ── Apply fold ────────────────────────────────
|
| 949 |
-
fold_dict = {
|
| 950 |
-
"type": action.fold_type,
|
| 951 |
-
"line": action.fold_line,
|
| 952 |
-
"angle": action.fold_angle,
|
| 953 |
-
"layer_select": action.layer_select,
|
| 954 |
-
}
|
| 955 |
-
|
| 956 |
-
try:
|
| 957 |
-
self._paper = apply_fold(self._paper, fold_dict)
|
| 958 |
-
self._fold_history.append({**fold_dict, "step": self._step_count})
|
| 959 |
-
except FoldError as e:
|
| 960 |
-
self._error = str(e)
|
| 961 |
-
return self._make_observation(done=True, reward=-5.0, render_urls={})
|
| 962 |
-
|
| 963 |
-
# ── Run physics ───────────────────────────────
|
| 964 |
-
try:
|
| 965 |
-
self._paper = simulate(self._paper, fold_percent=1.0)
|
| 966 |
-
except Exception as e:
|
| 967 |
-
self._error = f"Physics failed: {e}"
|
| 968 |
-
|
| 969 |
-
# ── Validate ──────────────────────────────────
|
| 970 |
-
self._validation = validate_state(self._paper)
|
| 971 |
-
|
| 972 |
-
# ── Compute metrics ───────────────────────────
|
| 973 |
-
self._metrics = compute_all_metrics(self._paper, self._task, self._validation)
|
| 974 |
-
|
| 975 |
-
# ── Render this step ──────────────────────────
|
| 976 |
-
render_urls = capture_step(self._paper, self._step_count, self._episode_dir)
|
| 977 |
-
|
| 978 |
-
# ── Check if episode should end ───────────────
|
| 979 |
-
done = False
|
| 980 |
-
reward = None
|
| 981 |
-
|
| 982 |
-
# Auto-end on max folds
|
| 983 |
-
if self._step_count >= self._task.get("max_folds", 50):
|
| 984 |
-
done = True
|
| 985 |
-
|
| 986 |
-
# Auto-end on critical failure
|
| 987 |
-
if self._validation["self_intersections"] > 0:
|
| 988 |
-
done = True
|
| 989 |
-
self._error = "Self-intersection detected"
|
| 990 |
-
|
| 991 |
-
if done:
|
| 992 |
-
return self._finalize_episode()
|
| 993 |
-
|
| 994 |
-
return self._make_observation(done=False, reward=None, render_urls=render_urls)
|
| 995 |
-
|
| 996 |
-
# ── finalize ──────────────────────────────────────
|
| 997 |
-
|
| 998 |
-
def _finalize_episode(self) -> OrigamiObservation:
|
| 999 |
-
"""End episode: compute final reward, generate animation GIF, export FOLD."""
|
| 1000 |
-
reward = self._compute_reward()
|
| 1001 |
-
|
| 1002 |
-
render_urls = capture_step(self._paper, self._step_count, self._episode_dir)
|
| 1003 |
-
|
| 1004 |
-
# Episode summary image
|
| 1005 |
-
summary_path = capture_episode_summary(
|
| 1006 |
-
self._paper, self._fold_history, self._task, self._metrics, self._episode_dir
|
| 1007 |
-
)
|
| 1008 |
-
render_urls["episode_summary"] = summary_path
|
| 1009 |
-
|
| 1010 |
-
# Fold animation GIF
|
| 1011 |
-
try:
|
| 1012 |
-
gif_path = record_fold_animation(
|
| 1013 |
-
create_flat_sheet(self._task["width"], self._task["height"], self._task["material"]),
|
| 1014 |
-
self._fold_history,
|
| 1015 |
-
f"{self._episode_dir}/animation.gif"
|
| 1016 |
-
)
|
| 1017 |
-
render_urls["episode_gif"] = gif_path
|
| 1018 |
-
except Exception:
|
| 1019 |
-
pass # non-critical
|
| 1020 |
-
|
| 1021 |
-
# FOLD JSON export
|
| 1022 |
-
fold_json = export_fold_json(self._paper, self._fold_history)
|
| 1023 |
-
fold_path = f"{self._episode_dir}/state.fold"
|
| 1024 |
-
with open(fold_path, "w") as f:
|
| 1025 |
-
json.dump(fold_json, f)
|
| 1026 |
-
render_urls["fold_json"] = fold_path
|
| 1027 |
-
|
| 1028 |
-
return self._make_observation(done=True, reward=reward, render_urls=render_urls)
|
| 1029 |
-
|
| 1030 |
-
# ── state ─────────────────────────────────────────
|
| 1031 |
-
|
| 1032 |
-
@property
|
| 1033 |
-
def state(self) -> OrigamiState:
|
| 1034 |
-
return OrigamiState(
|
| 1035 |
-
episode_id=self._episode_id,
|
| 1036 |
-
step_count=self._step_count,
|
| 1037 |
-
task_name=self._task["name"] if self._task else "",
|
| 1038 |
-
num_folds_applied=len(self._fold_history),
|
| 1039 |
-
is_valid=self._metrics.get("is_valid", True),
|
| 1040 |
-
total_reward=0.0,
|
| 1041 |
-
)
|
| 1042 |
-
|
| 1043 |
-
# ── helpers ───────────────────────────────────────
|
| 1044 |
-
|
| 1045 |
-
def _make_observation(self, done, reward, render_urls) -> OrigamiObservation:
|
| 1046 |
-
return OrigamiObservation(
|
| 1047 |
-
done=done,
|
| 1048 |
-
reward=reward,
|
| 1049 |
-
task=self._task or {},
|
| 1050 |
-
paper_state=self._paper.to_observation_dict() if self._paper else {},
|
| 1051 |
-
metrics=self._metrics,
|
| 1052 |
-
fold_history=self._fold_history,
|
| 1053 |
-
error=self._error,
|
| 1054 |
-
render_urls=render_urls,
|
| 1055 |
-
)
|
| 1056 |
-
|
| 1057 |
-
def _compute_reward(self) -> float:
|
| 1058 |
-
m = self._metrics
|
| 1059 |
-
reward = 0.0
|
| 1060 |
-
reward += m.get("compactness", 0) * 20.0
|
| 1061 |
-
if m.get("fits_target_box", False): reward += 10.0
|
| 1062 |
-
if m.get("is_deployable", False): reward += 5.0
|
| 1063 |
-
reward -= m.get("kawasaki_violations", 0) * 2.0
|
| 1064 |
-
reward -= m.get("maekawa_violations", 0) * 2.0
|
| 1065 |
-
reward -= m.get("self_intersections", 0) * 5.0
|
| 1066 |
-
reward -= m.get("fold_count", 0) * 0.5
|
| 1067 |
-
max_strain = m.get("max_strain", 0)
|
| 1068 |
-
limit = self._paper.material.max_strain if self._paper else 0.05
|
| 1069 |
-
if max_strain > limit: reward -= 3.0 * (max_strain / limit)
|
| 1070 |
-
return reward
|
| 1071 |
-
```
|
| 1072 |
-
|
| 1073 |
-
---
|
| 1074 |
-
|
| 1075 |
-
## 7. App + Docker (`server/app.py` + `server/Dockerfile`)
|
| 1076 |
-
|
| 1077 |
-
### `app.py`
|
| 1078 |
-
|
| 1079 |
-
```python
|
| 1080 |
-
"""FastAPI entry point — serves OpenEnv API + React frontend + renders."""
|
| 1081 |
-
from fastapi import FastAPI
|
| 1082 |
-
from fastapi.staticfiles import StaticFiles
|
| 1083 |
-
from openenv.core.env_server.http_server import create_app
|
| 1084 |
-
|
| 1085 |
-
from models import OrigamiAction, OrigamiObservation
|
| 1086 |
-
from origami_environment import OrigamiEnvironment
|
| 1087 |
-
|
| 1088 |
-
# OpenEnv app (handles /ws, /reset, /step, /state, /health)
|
| 1089 |
-
app = create_app(
|
| 1090 |
-
OrigamiEnvironment,
|
| 1091 |
-
OrigamiAction,
|
| 1092 |
-
OrigamiObservation,
|
| 1093 |
-
env_name="origami_env",
|
| 1094 |
-
)
|
| 1095 |
-
|
| 1096 |
-
# Serve rendered images/GIFs/FOLD exports
|
| 1097 |
-
app.mount("/renders", StaticFiles(directory="renders"), name="renders")
|
| 1098 |
-
app.mount("/export", StaticFiles(directory="renders"), name="export")
|
| 1099 |
-
|
| 1100 |
-
# Serve React frontend (built at Docker build time)
|
| 1101 |
-
app.mount("/", StaticFiles(directory="../web/dist", html=True), name="frontend")
|
| 1102 |
-
```
|
| 1103 |
-
|
| 1104 |
-
### `Dockerfile`
|
| 1105 |
-
|
| 1106 |
-
```dockerfile
|
| 1107 |
-
FROM ghcr.io/meta-pytorch/openenv-base:latest
|
| 1108 |
-
|
| 1109 |
-
# ── Install Node.js for React build ──────────────────
|
| 1110 |
-
RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/*
|
| 1111 |
-
|
| 1112 |
-
WORKDIR /app
|
| 1113 |
-
|
| 1114 |
-
# ── Python dependencies ──────────────────────────────
|
| 1115 |
-
COPY server/requirements.txt ./server/
|
| 1116 |
-
RUN pip install --no-cache-dir -r server/requirements.txt
|
| 1117 |
-
|
| 1118 |
-
# ── Build React frontend ─────────────────────────────
|
| 1119 |
-
COPY web/ ./web/
|
| 1120 |
-
RUN cd web && npm install && npm run build
|
| 1121 |
-
|
| 1122 |
-
# ── Copy server code ─────────────────────────────────
|
| 1123 |
-
COPY server/ ./server/
|
| 1124 |
-
|
| 1125 |
-
# ── Create renders directory ──────────────────────────
|
| 1126 |
-
RUN mkdir -p /app/server/renders
|
| 1127 |
-
|
| 1128 |
-
WORKDIR /app/server
|
| 1129 |
-
|
| 1130 |
-
EXPOSE 8000
|
| 1131 |
-
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
| 1132 |
-
```
|
| 1133 |
-
|
| 1134 |
-
### `requirements.txt`
|
| 1135 |
-
|
| 1136 |
-
```
|
| 1137 |
-
openenv-core[core]>=0.2.1
|
| 1138 |
-
numpy>=1.24
|
| 1139 |
-
scipy>=1.10
|
| 1140 |
-
pydantic>=2.0
|
| 1141 |
-
matplotlib>=3.7
|
| 1142 |
-
imageio>=2.31
|
| 1143 |
-
Pillow>=10.0
|
| 1144 |
-
```
|
| 1145 |
-
|
| 1146 |
-
### `openenv.yaml`
|
| 1147 |
-
|
| 1148 |
-
```yaml
|
| 1149 |
-
spec_version: 1
|
| 1150 |
-
name: origami_env
|
| 1151 |
-
type: space
|
| 1152 |
-
runtime: fastapi
|
| 1153 |
-
app: server.app:app
|
| 1154 |
-
port: 8000
|
| 1155 |
-
```
|
| 1156 |
-
|
| 1157 |
-
---
|
| 1158 |
-
|
| 1159 |
-
## 8. React Frontend (`web/`)
|
| 1160 |
-
|
| 1161 |
-
### Layout
|
| 1162 |
-
|
| 1163 |
-
```
|
| 1164 |
-
┌─────────────────────────────────────────────────────────────┐
|
| 1165 |
-
│ ORIGAMI RL ENVIRONMENT [Task: ▼] [Material: ▼] │
|
| 1166 |
-
├──────────────────────────┬──────────────────────────────────┤
|
| 1167 |
-
│ │ │
|
| 1168 |
-
│ CREASE PATTERN (2D) │ FOLDED STATE (3D) │
|
| 1169 |
-
│ │ │
|
| 1170 |
-
│ SVG │ R3F Canvas │
|
| 1171 |
-
│ M = red dashed │ OrbitControls (rotate/zoom) │
|
| 1172 |
-
│ V = blue dash-dot │ Vertex colors = strain │
|
| 1173 |
-
│ B = black solid │ DoubleSide material │
|
| 1174 |
-
│ │ Ambient + directional light │
|
| 1175 |
-
│ │ │
|
| 1176 |
-
├──────────────────────────┴──────────────────────────────────┤
|
| 1177 |
-
│ [|<] [<] [>] [>|] ████████░░░░ Step 4 / 8 │
|
| 1178 |
-
│ [Play] [Pause] Speed: [1x ▼] │
|
| 1179 |
-
├─────────────────────────────────────────────────────────────┤
|
| 1180 |
-
│ METRICS │
|
| 1181 |
-
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────────┐ │
|
| 1182 |
-
│ │Compactness│ │Max Strain │ │Fold Count │ │ Validity │ │
|
| 1183 |
-
│ │ 85.0% │ │ 0.021 │ │ 8 │ │ VALID │ │
|
| 1184 |
-
│ └───────────┘ └───────────┘ └───────────┘ └────────��────┘ │
|
| 1185 |
-
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────────┐ │
|
| 1186 |
-
│ │Deploy Rat.│ │ Energy │ │Pack Eff. │ │Fits Target │ │
|
| 1187 |
-
│ │ 15.2x │ │ 0.34 │ │ 72% │ │ YES │ │
|
| 1188 |
-
│ └───────────┘ └───────────┘ └───────────┘ └─────────────┘ │
|
| 1189 |
-
├─────────────────────────────────────────────────────────────┤
|
| 1190 |
-
│ [Screenshot PNG] [Record GIF] [Export FOLD] [Export OBJ]│
|
| 1191 |
-
└─────────────────────────────────────────────────────────────┘
|
| 1192 |
-
```
|
| 1193 |
-
|
| 1194 |
-
### Data Flow
|
| 1195 |
-
|
| 1196 |
-
```
|
| 1197 |
-
React App
|
| 1198 |
-
│
|
| 1199 |
-
├── useEnvironment() hook
|
| 1200 |
-
│ │
|
| 1201 |
-
│ ├── WebSocket connect to /ws
|
| 1202 |
-
│ ├── reset() → receives OrigamiObservation
|
| 1203 |
-
│ ├── step(action) → receives OrigamiObservation
|
| 1204 |
-
│ └── Parses: paper_state, metrics, fold_history, render_urls
|
| 1205 |
-
│
|
| 1206 |
-
├── CreasePattern.tsx
|
| 1207 |
-
│ └── Reads: paper_state.vertices_coords[:, :2], edges_vertices, edges_assignment
|
| 1208 |
-
│ Renders: SVG <line> elements with color/dash per assignment
|
| 1209 |
-
│
|
| 1210 |
-
├── FoldedView3D.tsx
|
| 1211 |
-
│ └── Reads: paper_state.vertices_coords[:, :3], faces_vertices, strain_per_vertex
|
| 1212 |
-
│ Renders: R3F <mesh> with BufferGeometry
|
| 1213 |
-
│ positions → Float32Array from vertices_coords
|
| 1214 |
-
│ index → Uint16Array from triangulated faces_vertices
|
| 1215 |
-
│ colors → Float32Array from Lut(strain_per_vertex) blue→red
|
| 1216 |
-
│
|
| 1217 |
-
├── FoldAnimation.tsx
|
| 1218 |
-
│ └── Reads: fold_history, paper_state per step
|
| 1219 |
-
│ Controls: step slider, play/pause, speed
|
| 1220 |
-
│ Interpolates fold_percent 0→1 via useFrame
|
| 1221 |
-
│
|
| 1222 |
-
├── MetricsDashboard.tsx
|
| 1223 |
-
│ └── Reads: metrics dict → renders cards
|
| 1224 |
-
│
|
| 1225 |
-
└── CaptureControls.tsx
|
| 1226 |
-
├── Screenshot: gl.domElement.toDataURL('image/png') (preserveDrawingBuffer=true)
|
| 1227 |
-
└── Record: canvas.captureStream(30) + MediaRecorder → WebM blob → download
|
| 1228 |
-
```
|
| 1229 |
-
|
| 1230 |
-
### Key R3F Pattern
|
| 1231 |
-
|
| 1232 |
-
```tsx
|
| 1233 |
-
// FoldedView3D.tsx — core rendering
|
| 1234 |
-
<Canvas gl={{ preserveDrawingBuffer: true, antialias: true }}>
|
| 1235 |
-
<PerspectiveCamera makeDefault position={[0, 2, 3]} />
|
| 1236 |
-
<OrbitControls />
|
| 1237 |
-
<ambientLight intensity={0.4} />
|
| 1238 |
-
<directionalLight position={[5, 5, 5]} intensity={0.8} />
|
| 1239 |
-
|
| 1240 |
-
<mesh>
|
| 1241 |
-
<bufferGeometry>
|
| 1242 |
-
<bufferAttribute attach="attributes-position"
|
| 1243 |
-
array={positionsFloat32} count={vertexCount} itemSize={3} />
|
| 1244 |
-
<bufferAttribute attach="attributes-color"
|
| 1245 |
-
array={strainColorsFloat32} count={vertexCount} itemSize={3} />
|
| 1246 |
-
<bufferAttribute attach="index"
|
| 1247 |
-
array={indicesUint16} count={indexCount} itemSize={1} />
|
| 1248 |
-
</bufferGeometry>
|
| 1249 |
-
<meshStandardMaterial vertexColors side={DoubleSide} />
|
| 1250 |
-
</mesh>
|
| 1251 |
-
|
| 1252 |
-
{/* Crease lines in 3D */}
|
| 1253 |
-
{edges.map((edge, i) => (
|
| 1254 |
-
<Line key={i}
|
| 1255 |
-
points={[vertices[edge[0]], vertices[edge[1]]]}
|
| 1256 |
-
color={assignmentColor(assignments[i])}
|
| 1257 |
-
lineWidth={assignments[i] === 'B' ? 2 : 1}
|
| 1258 |
-
dashed={assignments[i] === 'M' || assignments[i] === 'V'}
|
| 1259 |
-
/>
|
| 1260 |
-
))}
|
| 1261 |
-
</Canvas>
|
| 1262 |
-
```
|
| 1263 |
-
|
| 1264 |
-
---
|
| 1265 |
-
|
| 1266 |
-
## 9. Client (`client/`)
|
| 1267 |
-
|
| 1268 |
-
### `client.py`
|
| 1269 |
-
|
| 1270 |
-
```python
|
| 1271 |
-
class OrigamiEnvClient(EnvClient[OrigamiAction, OrigamiObservation, OrigamiState]):
|
| 1272 |
-
|
| 1273 |
-
def _step_payload(self, action: OrigamiAction) -> Dict:
|
| 1274 |
-
return {
|
| 1275 |
-
"fold_type": action.fold_type,
|
| 1276 |
-
"fold_line": action.fold_line,
|
| 1277 |
-
"fold_angle": action.fold_angle,
|
| 1278 |
-
"layer_select": action.layer_select,
|
| 1279 |
-
"metadata": action.metadata,
|
| 1280 |
-
}
|
| 1281 |
-
|
| 1282 |
-
def _parse_result(self, payload: Dict) -> StepResult[OrigamiObservation]:
|
| 1283 |
-
obs = OrigamiObservation(**payload.get("observation", payload))
|
| 1284 |
-
return StepResult(observation=obs, reward=obs.reward)
|
| 1285 |
-
|
| 1286 |
-
def _parse_state(self, payload: Dict) -> OrigamiState:
|
| 1287 |
-
return OrigamiState(**payload)
|
| 1288 |
-
```
|
| 1289 |
-
|
| 1290 |
-
### `reward_functions.py`
|
| 1291 |
-
|
| 1292 |
-
Three reward functions for GRPO training. These run on the Colab client side, NOT on the server.
|
| 1293 |
-
|
| 1294 |
-
The strategy function extracted from LLM output calls `step()` in a loop (same pattern as 2048):
|
| 1295 |
-
|
| 1296 |
-
```python
|
| 1297 |
-
def _execute_strategy(strategy_fn, openenv_process):
|
| 1298 |
-
"""
|
| 1299 |
-
Execute a fold_strategy against the environment.
|
| 1300 |
-
strategy_fn takes paper_state dict, returns one fold dict or None (stop).
|
| 1301 |
-
Loops until done or strategy returns None.
|
| 1302 |
-
"""
|
| 1303 |
-
result = openenv_process.reset()
|
| 1304 |
-
obs = result.observation
|
| 1305 |
-
|
| 1306 |
-
while not obs.done:
|
| 1307 |
-
paper_state = obs.paper_state
|
| 1308 |
-
fold = strategy_fn(paper_state)
|
| 1309 |
-
|
| 1310 |
-
if fold is None:
|
| 1311 |
-
# Strategy says stop
|
| 1312 |
-
action = OrigamiAction(fold_type="stop", fold_line={"start":[0,0],"end":[0,0]})
|
| 1313 |
-
else:
|
| 1314 |
-
action = OrigamiAction(
|
| 1315 |
-
fold_type=fold.get("type", "valley"),
|
| 1316 |
-
fold_line=fold.get("line", {"start":[0,0.5],"end":[1,0.5]}),
|
| 1317 |
-
fold_angle=fold.get("angle", 180),
|
| 1318 |
-
)
|
| 1319 |
-
|
| 1320 |
-
result = openenv_process.step(action)
|
| 1321 |
-
obs = result.observation
|
| 1322 |
-
|
| 1323 |
-
return obs
|
| 1324 |
-
```
|
| 1325 |
-
|
| 1326 |
-
Reward functions: `code_valid`, `no_cheating`, `fold_quality` — same structure as 2048 (extract function, sandbox, execute, score from metrics).
|
| 1327 |
-
|
| 1328 |
-
---
|
| 1329 |
-
|
| 1330 |
-
## 10. Task System (`server/tasks.py`)
|
| 1331 |
-
|
| 1332 |
-
### Curriculum (4 difficulty levels)
|
| 1333 |
-
|
| 1334 |
-
| Level | Task | Material | Target Ratio | Max Folds | Key Challenge |
|
| 1335 |
-
|-------|------|----------|-------------|-----------|---------------|
|
| 1336 |
-
| 1 | half_fold | paper | 0.50 | 3 | Learn the format |
|
| 1337 |
-
| 1 | quarter_fold | paper | 0.25 | 5 | Two perpendicular folds |
|
| 1338 |
-
| 2 | letter_fold | paper | 0.33 | 5 | Tri-fold, parallel lines |
|
| 1339 |
-
| 2 | map_fold | paper | 0.125 | 8 | Grid fold, must deploy |
|
| 1340 |
-
| 3 | solar_panel | mylar | 0.05 | 20 | Miura-ori discovery, deployability |
|
| 1341 |
-
| 3 | shelter_wall | aluminum | 0.10 | 15 | Rigid material, strain limits |
|
| 1342 |
-
| 4 | stent | nitinol | 0.09 | 25 | Cylindrical target shape, superelastic |
|
| 1343 |
-
|
| 1344 |
-
### How Tasks Drive Reward
|
| 1345 |
-
|
| 1346 |
-
- **target_ratio** → compactness reward signal
|
| 1347 |
-
- **target_box** → fits_target_box bonus (+10.0)
|
| 1348 |
-
- **must_deploy** → deployability bonus (+5.0)
|
| 1349 |
-
- **material.max_strain** → strain penalty threshold
|
| 1350 |
-
- **max_folds** → episode length limit
|
| 1351 |
-
- **target_shape** → shape similarity metrics (chamfer/hausdorff)
|
| 1352 |
-
|
| 1353 |
-
---
|
| 1354 |
-
|
| 1355 |
-
## 11. API Reference
|
| 1356 |
-
|
| 1357 |
-
### Endpoints (provided by OpenEnv + our extensions)
|
| 1358 |
-
|
| 1359 |
-
| Endpoint | Method | Purpose |
|
| 1360 |
-
|----------|--------|---------|
|
| 1361 |
-
| `/health` | GET | `{"status": "ok", "env_name": "origami_env"}` |
|
| 1362 |
-
| `/ws` | WebSocket | Main OpenEnv communication channel |
|
| 1363 |
-
| `/reset` | POST | Reset environment, get initial observation |
|
| 1364 |
-
| `/step` | POST | Send action, get observation |
|
| 1365 |
-
| `/state` | GET | Get current OrigamiState |
|
| 1366 |
-
| `/renders/{episode_id}/*` | GET | Serve screenshots, GIFs, FOLD exports |
|
| 1367 |
-
| `/` | GET | React frontend (static) |
|
| 1368 |
-
|
| 1369 |
-
### WebSocket Message Format
|
| 1370 |
-
|
| 1371 |
-
```json
|
| 1372 |
-
// Client → Server (step)
|
| 1373 |
-
{
|
| 1374 |
-
"type": "step",
|
| 1375 |
-
"action": {
|
| 1376 |
-
"fold_type": "valley",
|
| 1377 |
-
"fold_line": {"start": [0, 0.5], "end": [1, 0.5]},
|
| 1378 |
-
"fold_angle": 180,
|
| 1379 |
-
"layer_select": "all"
|
| 1380 |
-
}
|
| 1381 |
-
}
|
| 1382 |
-
|
| 1383 |
-
// Server → Client (observation)
|
| 1384 |
-
{
|
| 1385 |
-
"type": "observation",
|
| 1386 |
-
"observation": {
|
| 1387 |
-
"done": false,
|
| 1388 |
-
"reward": null,
|
| 1389 |
-
"task": {...},
|
| 1390 |
-
"paper_state": {...},
|
| 1391 |
-
"metrics": {...},
|
| 1392 |
-
"fold_history": [...],
|
| 1393 |
-
"error": null,
|
| 1394 |
-
"render_urls": {
|
| 1395 |
-
"crease_2d": "/renders/ep_abc123/crease_step_1.png",
|
| 1396 |
-
"folded_3d": "/renders/ep_abc123/folded_step_1.png",
|
| 1397 |
-
"strain_heatmap": "/renders/ep_abc123/strain_step_1.png"
|
| 1398 |
-
}
|
| 1399 |
-
}
|
| 1400 |
-
}
|
| 1401 |
-
```
|
| 1402 |
-
|
| 1403 |
-
---
|
| 1404 |
-
|
| 1405 |
-
## 12. Deployment
|
| 1406 |
-
|
| 1407 |
-
### Push to HF Spaces
|
| 1408 |
-
|
| 1409 |
-
```bash
|
| 1410 |
-
cd origami_env/
|
| 1411 |
-
openenv push --repo-id <username>/origami-env
|
| 1412 |
-
```
|
| 1413 |
-
|
| 1414 |
-
### Or manually via Docker
|
| 1415 |
-
|
| 1416 |
-
```bash
|
| 1417 |
-
# Build
|
| 1418 |
-
docker build -t origami-env -f server/Dockerfile .
|
| 1419 |
-
|
| 1420 |
-
# Run locally
|
| 1421 |
-
docker run -p 8000:8000 origami-env
|
| 1422 |
-
|
| 1423 |
-
# Test
|
| 1424 |
-
curl http://localhost:8000/health
|
| 1425 |
-
```
|
| 1426 |
-
|
| 1427 |
-
### HF Space README header
|
| 1428 |
-
|
| 1429 |
-
```yaml
|
| 1430 |
-
---
|
| 1431 |
-
title: Origami RL Environment
|
| 1432 |
-
emoji: 🔬
|
| 1433 |
-
colorFrom: blue
|
| 1434 |
-
colorTo: red
|
| 1435 |
-
sdk: docker
|
| 1436 |
-
app_port: 8000
|
| 1437 |
-
pinned: true
|
| 1438 |
-
---
|
| 1439 |
-
```
|
| 1440 |
-
|
| 1441 |
-
---
|
| 1442 |
-
|
| 1443 |
-
## 13. Testing Checklist
|
| 1444 |
-
|
| 1445 |
-
```bash
|
| 1446 |
-
# 1. Engine standalone
|
| 1447 |
-
python -c "
|
| 1448 |
-
from server.engine.paper import create_flat_sheet
|
| 1449 |
-
from server.engine.materials import MATERIALS
|
| 1450 |
-
p = create_flat_sheet(1.0, 1.0, MATERIALS['paper'])
|
| 1451 |
-
print(f'Vertices: {p.vertices_coords.shape}, Edges: {p.edges_vertices.shape}')
|
| 1452 |
-
"
|
| 1453 |
-
|
| 1454 |
-
# 2. Fold works
|
| 1455 |
-
python -c "
|
| 1456 |
-
from server.engine.paper import create_flat_sheet
|
| 1457 |
-
from server.engine.fold import apply_fold
|
| 1458 |
-
from server.engine.materials import MATERIALS
|
| 1459 |
-
p = create_flat_sheet(1.0, 1.0, MATERIALS['paper'])
|
| 1460 |
-
p = apply_fold(p, {'type':'valley','line':{'start':[0,0.5],'end':[1,0.5]},'angle':180})
|
| 1461 |
-
print(f'After fold: {p.vertices_coords.shape[0]} vertices, fold_count={p.fold_count}')
|
| 1462 |
-
"
|
| 1463 |
-
|
| 1464 |
-
# 3. Physics runs
|
| 1465 |
-
python -c "
|
| 1466 |
-
from server.engine.paper import create_flat_sheet
|
| 1467 |
-
from server.engine.fold import apply_fold
|
| 1468 |
-
from server.engine.physics import simulate
|
| 1469 |
-
from server.engine.materials import MATERIALS
|
| 1470 |
-
p = create_flat_sheet(1.0, 1.0, MATERIALS['paper'])
|
| 1471 |
-
p = apply_fold(p, {'type':'valley','line':{'start':[0,0.5],'end':[1,0.5]},'angle':180})
|
| 1472 |
-
p = simulate(p)
|
| 1473 |
-
print(f'Max strain: {p.strain_per_vertex.max():.6f}, Energy: {p.energy}')
|
| 1474 |
-
"
|
| 1475 |
-
|
| 1476 |
-
# 4. Validation works
|
| 1477 |
-
python -c "
|
| 1478 |
-
from server.engine.validation import validate_state
|
| 1479 |
-
# ... create + fold paper ...
|
| 1480 |
-
report = validate_state(p)
|
| 1481 |
-
print(f'Valid: {report[\"is_valid\"]}, Kawasaki: {report[\"kawasaki_violations\"]}')
|
| 1482 |
-
"
|
| 1483 |
-
|
| 1484 |
-
# 5. Metrics computed
|
| 1485 |
-
python -c "
|
| 1486 |
-
from server.engine.metrics import compute_all_metrics
|
| 1487 |
-
# ... create + fold + validate paper ...
|
| 1488 |
-
m = compute_all_metrics(p, task, validation)
|
| 1489 |
-
print(f'Compactness: {m[\"compactness\"]:.3f}, Fits box: {m[\"fits_target_box\"]}')
|
| 1490 |
-
"
|
| 1491 |
-
|
| 1492 |
-
# 6. Renderer works
|
| 1493 |
-
python -c "
|
| 1494 |
-
from server.renderer.render_2d import render_crease_pattern
|
| 1495 |
-
from server.renderer.render_3d import render_folded_state
|
| 1496 |
-
# ... create + fold paper ...
|
| 1497 |
-
render_crease_pattern(p, 'test_crease.png')
|
| 1498 |
-
render_folded_state(p, 'test_folded.png')
|
| 1499 |
-
print('Renders saved')
|
| 1500 |
-
"
|
| 1501 |
-
|
| 1502 |
-
# 7. Server starts
|
| 1503 |
-
cd server && uvicorn app:app --port 8000 &
|
| 1504 |
-
curl http://localhost:8000/health
|
| 1505 |
-
|
| 1506 |
-
# 8. Reset + step via API
|
| 1507 |
-
python -c "
|
| 1508 |
-
from client.client import OrigamiEnvClient
|
| 1509 |
-
c = OrigamiEnvClient('ws://localhost:8000')
|
| 1510 |
-
obs = c.reset()
|
| 1511 |
-
print(f'Task: {obs.task[\"name\"]}, Sheet: {obs.paper_state[\"num_vertices\"]} vertices')
|
| 1512 |
-
"
|
| 1513 |
-
|
| 1514 |
-
# 9. React frontend builds
|
| 1515 |
-
cd web && npm install && npm run build
|
| 1516 |
-
# Check web/dist/index.html exists
|
| 1517 |
-
|
| 1518 |
-
# 10. Docker builds and runs
|
| 1519 |
-
docker build -t origami-env -f server/Dockerfile .
|
| 1520 |
-
docker run -p 8000:8000 origami-env
|
| 1521 |
-
curl http://localhost:8000/health
|
| 1522 |
-
# Open http://localhost:8000 in browser — see React UI
|
| 1523 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
research 2/research.md
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
# Origami RL Environment — Research Index
|
| 2 |
-
|
| 3 |
-
## What We're Building
|
| 4 |
-
An OpenEnv environment where an LLM learns to design optimal origami fold patterns — solar panel packing, deployable structures, medical stents. LLM generates a `fold_strategy()` function (code-as-policy), executed against a bar-and-hinge physics simulation.
|
| 5 |
-
|
| 6 |
-
---
|
| 7 |
-
|
| 8 |
-
## Architecture (START HERE)
|
| 9 |
-
|
| 10 |
-
| File | What's In It |
|
| 11 |
-
|------|-------------|
|
| 12 |
-
| **[plan/architecture.md](plan/architecture.md)** | **Full architecture: action space, state, physics, rewards, rendering, project structure, implementation order** |
|
| 13 |
-
| **[plan/openenv_arch.md](plan/openenv_arch.md)** | **Complete OpenEnv environment: repo structure, Pydantic models, engine (paper/fold/physics/validation/metrics/materials), renderer (2D/3D/screenshots/GIF recording/export), environment class, React frontend, app+Docker, client, task system, API reference, deployment, testing** |
|
| 14 |
-
|
| 15 |
-
### Decisions (Locked)
|
| 16 |
-
|
| 17 |
-
| Decision | Choice |
|
| 18 |
-
|----------|--------|
|
| 19 |
-
| LLM interaction | Code-as-policy (LLM writes `fold_strategy()` function) |
|
| 20 |
-
| Action space | Named fold ops (valley/mountain + fold line + angle) |
|
| 21 |
-
| State format | FOLD-compatible JSON |
|
| 22 |
-
| Physics engine | Bar-and-hinge model (NumPy port of Ghassaei) |
|
| 23 |
-
| Validation | Kawasaki + Maekawa + triangle-triangle intersection |
|
| 24 |
-
| Primary task | Solar panel packing (Miura-ori discovery) |
|
| 25 |
-
| Training render | matplotlib headless |
|
| 26 |
-
| Demo render | React + @react-three/fiber |
|
| 27 |
-
| Training | GRPO via TRL + Unsloth on Colab |
|
| 28 |
-
| Deployment | Docker Space on HF Spaces |
|
| 29 |
-
|
| 30 |
-
---
|
| 31 |
-
|
| 32 |
-
## OpenEnv (The Framework)
|
| 33 |
-
|
| 34 |
-
| File | What's In It |
|
| 35 |
-
|------|-------------|
|
| 36 |
-
| [openenv/overview.md](openenv/overview.md) | OpenEnv architecture, API, types, project structure, deployment |
|
| 37 |
-
| [openenv/2048_pattern.md](openenv/2048_pattern.md) | Code-as-policy pattern, reward functions, GRPO training |
|
| 38 |
-
| [openenv/2048_example.py](openenv/2048_example.py) | Full extracted code from Unsloth 2048 Colab (636 lines) |
|
| 39 |
-
|
| 40 |
-
---
|
| 41 |
-
|
| 42 |
-
## Origami Domain Knowledge
|
| 43 |
-
|
| 44 |
-
### Quick Reference
|
| 45 |
-
| File | What's In It |
|
| 46 |
-
|------|-------------|
|
| 47 |
-
| [origami/fold_types_deep.md](origami/fold_types_deep.md) | **All fold operations**, Huzita-Justin axioms, crane step-by-step (31 steps), compression patterns (Miura-ori, Kresling, flasher), complexity scaling |
|
| 48 |
-
| [origami/math_physics_deep.md](origami/math_physics_deep.md) | **Kawasaki/Maekawa theorems** with code, bar-and-hinge model, energy formulas, strain computation, rigid foldability, computational complexity table |
|
| 49 |
-
| [origami/rendering_research.md](origami/rendering_research.md) | **Rendering options**: Ghassaei simulator, OrigamiOdyssey (R3F), Three.js in React, Gradio integration, recording |
|
| 50 |
-
| [origami/applications_deep.md](origami/applications_deep.md) | **Real-world apps**: NASA solar panels, JWST, stents, self-folding robots, metamaterials |
|
| 51 |
-
|
| 52 |
-
### Earlier Research (Summaries)
|
| 53 |
-
| File | What's In It |
|
| 54 |
-
|------|-------------|
|
| 55 |
-
| [origami/simulation_engines.md](origami/simulation_engines.md) | Ghassaei, rigid-origami Gym env, SWOMPS, Tachi |
|
| 56 |
-
| [origami/fold_format.md](origami/fold_format.md) | FOLD file format — JSON standard for crease patterns |
|
| 57 |
-
| [origami/physics.md](origami/physics.md) | Physics summary (Kawasaki, Maekawa, simulation approaches) |
|
| 58 |
-
| [origami/materials.md](origami/materials.md) | Material properties (paper, mylar, aluminum), stress viz |
|
| 59 |
-
| [origami/metrics.md](origami/metrics.md) | All metrics: validity, compactness, stress, shape similarity |
|
| 60 |
-
| [origami/existing_work.md](origami/existing_work.md) | Prior work: IJCAI 2023, Nature 2022, UCLA robotics |
|
| 61 |
-
| [origami/python_tools.md](origami/python_tools.md) | Libraries: rigid-origami, PyOri, numpy, trimesh |
|
| 62 |
-
|
| 63 |
-
---
|
| 64 |
-
|
| 65 |
-
## Deliverables Checklist
|
| 66 |
-
|
| 67 |
-
- [ ] Engine: paper.py, fold_engine.py, physics.py, validation.py, metrics.py
|
| 68 |
-
- [ ] OpenEnv server: models.py, origami_environment.py, app.py, Dockerfile
|
| 69 |
-
- [ ] Reward functions: code_valid, physically_valid, fold_quality
|
| 70 |
-
- [ ] Training notebook: Colab with GRPO + Unsloth/TRL
|
| 71 |
-
- [ ] Rendering: matplotlib (training) + React/R3F (demo)
|
| 72 |
-
- [ ] Deploy to HF Spaces
|
| 73 |
-
- [ ] 1-minute demo video on YouTube
|
| 74 |
-
- [ ] Public GitHub repo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|