OpenEnv RL environment for origami folding. Submit FOLD crease patterns, get physics simulation + shape similarity reward.
Rewards inspired by AlphaFold — chamfer distance shape matching with rotational alignment across 24 orientations.
How It Works
1
Reset
Pick a task → get target shape and flat paper starting state
2
Step
Submit a FOLD JSON crease pattern describing your fold
3
Reward
shape_similarity × 20.0 — score from 0 to 20
FOLD Format
vertices_coords
[[x,y], ...]
Vertex positionsrequired
edges_vertices
[[v1,v2], ...]
Edge connectivityrequired
edges_assignment
["B"|"M"|"V"|"F"|"U"]
Edge types. Need ≥1 M or V, ≥1 Brequired
edges_foldAngle
[degrees, ...]
Fold angles. Defaults: V→180°, M→−180°optional
faces_vertices
[[v0,v1,...], ...]
Face polygons. Auto-computed if missingoptional
Observation
shape_similarity
float 0.0–1.0
Procrustes match to target shape
final_positions
[[x,y,z], ...]
Folded vertex positions
target_positions
[[x,y,z], ...]
Expected target positions
max_strain
float
Edge deformation metric
is_stable
bool
Convergence flag
reward
float
similarity × 20.0, or −2.0 on error
Rewards
The environment computes reward from the physics simulation result. Two reward functions are available for training:
valid_fold
format reward
+1.0 valid FOLD JSON, −0.5 parseable but invalid structure, −2.0 not parseable
shape_match
main reward
similarity × 20.0 (0–20). −1.0 if simulation fails, −2.0 if invalid
How shape_similarity is computed
1. Simulate
Run physics engine on submitted crease pattern → get final 3D vertex positions
2. Center
Center both predicted and target point clouds at origin
3. Align
Try 24 rotation alignments (90° rotations + mirrors) to handle equivalent orientations
4. Chamfer
Bidirectional nearest-neighbor distance, normalized by bounding box diagonal
5. Score
similarity = 1 − (chamfer / diagonal), clamped to [0, 1]. Reward = similarity × 20
max_strain measures edge length deviation after folding (0 = no deformation). is_stable indicates whether the simulation converged.
API Reference
WebSocket
WS
/ws
Persistent connection
Send: Reset
{"type": "reset", "data": {"task_name": "triangle"}}
Send: Step
{"type": "step", "data": {"fold_data": {...}}}
Receive: Observation
{"type": "observation", "data": {"reward": 20.0, "done": true, ...}}
REST
POST
/sessions
Create session
POST
/sessions/{id}/reset
Reset with task_name
POST
/sessions/{id}/step
Submit fold action
GET
/tasks
List all tasks
GET
/tasks/{name}
Task detail + target fold
Quick Start
from origami_env.client import OrigamiEnv
from origami_env.models import OrigamiAction
with OrigamiEnv(base_url="http://localhost:8000") as env:
env.reset(task_name="triangle")
result = env.step(OrigamiAction(fold_data={
"vertices_coords": [[0,0],[1,0],[1,1],[0,1]],
"edges_vertices": [[0,1],[1,2],[2,3],[3,0],[0,2]],
"edges_assignment": ["B","B","B","B","V"],
"edges_foldAngle": [0,0,0,0,180]
}))
print(result.observation.shape_similarity) # 1.0
import requests
# Fetch available tasks
tasks = requests.get("http://localhost:8000/tasks").json()
for name, info in tasks.items():
print(f"{name}: difficulty {info['difficulty']}")
# Get target crease pattern for this task
detail = requests.get(f"http://localhost:8000/tasks/{name}").json()
target = detail["target_fold"]
# Create session, reset, step
s = requests.post("http://localhost:8000/sessions").json()
sid = s["session_id"]
requests.post(f"http://localhost:8000/sessions/{sid}/reset", json={"task_name": name})
obs = requests.post(f"http://localhost:8000/sessions/{sid}/step", json={"fold_data": target}).json()
print(f" reward: {obs['reward']}, similarity: {obs['shape_similarity']}")