ianalin123 commited on
Commit
ca61c8d
·
1 Parent(s): f8d2bab

feat: update engine modules, remove research 2 docs

Browse files
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
- return Paper(
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