sanjuhs commited on
Commit
0b2c776
·
verified ·
1 Parent(s): 687c30a

Improve CADForge before-after repair demo

Browse files
Files changed (1) hide show
  1. server/app.py +105 -44
server/app.py CHANGED
@@ -29,11 +29,11 @@ DEMO_TASKS = {
29
  "broken_code": r'''
30
  import cadquery as cq
31
 
32
- # Broken seed: plausible text, but the assembly never closes.
33
- plate = cq.Workplane("XY").box(44, 44, 4)
34
- stem = cq.Workplane("XY").cylinder(16, 5).translate((0, 0, 10))
35
- fork = cq.Workplane("XY").box(32, 18, 24).translate((0, 0, 20))
36
- fixture = plate.union(stem).union(
37
  '''.strip(),
38
  "code": r'''
39
  import cadquery as cq
@@ -84,40 +84,60 @@ fixture = plate.union(stem).union(fork).union(wheel).union(axle).clean()
84
  "broken_code": r'''
85
  import cadquery as cq
86
 
87
- # Broken seed: it uses a plausible but unsupported helper and never makes fixture.
88
- outer_radius = 60
89
- inner_radius = 24
90
- ring = cq.Workplane("XY").annulus(inner_radius, outer_radius).extrude(8)
 
 
 
91
  '''.strip(),
92
  "code": r'''
93
  import cadquery as cq
94
 
95
- slot_count = 12
96
- outer_radius = 60.0
97
- inner_radius = 24.0
98
- shaft_radius = 11.0
99
- thickness = 8.0
100
- tooth_length = 24.0
101
- tooth_width = 10.0
102
-
103
- ring = cq.Workplane("XY").circle(outer_radius).extrude(thickness).cut(
104
- cq.Workplane("XY").circle(inner_radius).extrude(thickness + 2).translate((0, 0, -1))
105
- )
106
-
107
- teeth = None
108
- for index in range(slot_count):
109
- angle = 360.0 * index / slot_count
 
 
 
110
  tooth = (
111
  cq.Workplane("XY")
112
- .center(inner_radius + tooth_length / 2, 0)
113
- .rect(tooth_length, tooth_width)
114
- .extrude(thickness)
115
- .rotate((0, 0, 0), (0, 0, 1), angle)
116
  )
117
- teeth = tooth if teeth is None else teeth.union(tooth)
118
-
119
- shaft_hole = cq.Workplane("XY").circle(shaft_radius).extrude(thickness + 2).translate((0, 0, -1))
120
- fixture = ring.union(teeth).cut(shaft_hole).clean()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  '''.strip(),
122
  },
123
  }
@@ -280,7 +300,7 @@ SPACE_HTML = r'''
280
  <div class="viewer-head">
281
  <div>
282
  <h2 id="viewerTitle">Buildable CAD preview</h2>
283
- <p id="viewerStatus">Choose a task. CADForge will run a broken seed, diagnose it, repair it, verify it, and render the final STL.</p>
284
  </div>
285
  <div class="demo-controls">
286
  <select id="taskSelect">
@@ -292,7 +312,7 @@ SPACE_HTML = r'''
292
  </div>
293
  <div id="viewer"></div>
294
  <div class="trace-strip" id="traceStrip">
295
- <div class="trace-item"><strong>Step 0</strong><span>Waiting for broken seed.</span></div>
296
  <div class="trace-item"><strong>Step 1</strong><span>Waiting for repaired CAD.</span></div>
297
  </div>
298
  <div class="viewer-foot">
@@ -359,7 +379,7 @@ SPACE_HTML = r'''
359
  const viewer = document.querySelector("#viewer");
360
  const scene = new THREE.Scene();
361
  scene.background = new THREE.Color(0xeff4f7);
362
- const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 5000);
363
  camera.position.set(180, -220, 170);
364
  const renderer = new THREE.WebGLRenderer({ antialias: true });
365
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
@@ -391,16 +411,19 @@ SPACE_HTML = r'''
391
  box.getSize(size);
392
  box.getCenter(center);
393
  const maxDim = Math.max(size.x, size.y, size.z, 1);
 
 
394
  camera.position.set(center.x + maxDim * 1.25, center.y - maxDim * 1.55, center.z + maxDim * 1.05);
 
395
  controls.target.copy(center);
396
  controls.update();
397
  }
398
 
399
  async function runDemo() {
400
  const taskId = document.querySelector("#taskSelect").value;
401
- document.querySelector("#viewerStatus").textContent = "Running broken seed, repair, CadQuery build, and reward...";
402
  document.querySelector("#traceStrip").innerHTML = `
403
- <div class="trace-item"><strong>Step 0</strong><span>Evaluating broken seed...</span></div>
404
  <div class="trace-item"><strong>Step 1</strong><span>Waiting for repaired CAD...</span></div>
405
  `;
406
  const response = await fetch("/api/space/repair-loop", {
@@ -424,13 +447,34 @@ SPACE_HTML = r'''
424
  document.querySelector("#semanticMetric").textContent = Number(payload.final.reward.semantic_parts || 0).toFixed(3);
425
  document.querySelector("#rewardJson").textContent = JSON.stringify(payload, null, 2);
426
 
427
- const geometry = await new STLLoader().loadAsync(payload.final.stl_url + "?t=" + Date.now());
428
- geometry.computeVertexNormals();
429
- if (mesh) scene.remove(mesh);
430
- mesh = new THREE.Mesh(
431
- geometry,
432
- new THREE.MeshStandardMaterial({ color: taskId.includes("stator") ? 0x7f8c8d : 0x3c8dbc, roughness: 0.58, metalness: 0.45 })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  );
 
 
434
  scene.add(mesh);
435
  frameObject(mesh);
436
  }
@@ -586,7 +630,7 @@ async def run_space_repair_loop(request: Request) -> JSONResponse:
586
  artifact_dir = Path(str(repaired.get("artifacts_dir") or _loop_artifact_dir(task_id, "repaired")))
587
  stl_path = _stl_path(artifact_dir)
588
  steps = [
589
- _step_payload("broken seed", broken, "CADForge rejects the first attempt and emits failure feedback."),
590
  _step_payload("repaired CAD", repaired, "The repaired candidate is rebuilt and rescored."),
591
  ]
592
  if not repaired.get("ok") or not stl_path.exists():
@@ -611,6 +655,13 @@ async def run_space_repair_loop(request: Request) -> JSONResponse:
611
  "delta_reward": float(repaired_reward.get("total", 0.0) or 0.0)
612
  - float(broken_reward.get("total", 0.0) or 0.0),
613
  "steps": steps,
 
 
 
 
 
 
 
614
  "final": {
615
  "reward": repaired_reward,
616
  "notes": repaired.get("notes", []),
@@ -641,6 +692,16 @@ def get_space_loop_stl(task_id: str) -> FileResponse:
641
  return FileResponse(stl_path, media_type="model/stl", filename=f"{safe_task}-repaired.stl")
642
 
643
 
 
 
 
 
 
 
 
 
 
 
644
  def main(host: str = "0.0.0.0", port: int = 8000) -> None:
645
  import uvicorn
646
 
 
29
  "broken_code": r'''
30
  import cadquery as cq
31
 
32
+ # Weak seed: buildable, but it is missing wheel, axle, holes, and a real fork.
33
+ plate = cq.Workplane("XY").box(44, 44, 4).translate((0, 0, 2))
34
+ stem = cq.Workplane("XY").cylinder(14, 5).translate((0, 0, 11))
35
+ stub = cq.Workplane("XY").box(24, 16, 12).translate((0, 0, 24))
36
+ fixture = plate.union(stem).union(stub).clean()
37
  '''.strip(),
38
  "code": r'''
39
  import cadquery as cq
 
84
  "broken_code": r'''
85
  import cadquery as cq
86
 
87
+ # Weak seed: buildable disk with a center bore, but no teeth or slot structure.
88
+ outer_radius = 60.0
89
+ shaft_radius = 12.0
90
+ thickness = 8.0
91
+ disk = cq.Workplane("XY").circle(outer_radius).extrude(thickness)
92
+ bore = cq.Workplane("XY").circle(shaft_radius).extrude(thickness + 2).translate((0, 0, -1))
93
+ fixture = disk.cut(bore).clean()
94
  '''.strip(),
95
  "code": r'''
96
  import cadquery as cq
97
 
98
+ # Editable axial motor stator concept with twelve radial teeth and center bore.
99
+ stator_slot_count = 12
100
+ stator_outer_radius = 60.0
101
+ stator_inner_radius = 24.0
102
+ stator_shaft_radius = 11.0
103
+ stator_thickness = 8.0
104
+ radial_tooth_length = 28.0
105
+ radial_tooth_width = 10.0
106
+ back_iron_width = stator_outer_radius - stator_inner_radius
107
+
108
+ def make_stator_ring():
109
+ outer = cq.Workplane("XY").circle(stator_outer_radius).extrude(stator_thickness)
110
+ inner_cut = cq.Workplane("XY").circle(stator_inner_radius).extrude(stator_thickness + 2).translate((0, 0, -1))
111
+ return outer.cut(inner_cut)
112
+
113
+ def make_radial_tooth(index):
114
+ angle = 360.0 * index / stator_slot_count
115
+ tooth_center = stator_inner_radius + radial_tooth_length / 2.0
116
  tooth = (
117
  cq.Workplane("XY")
118
+ .center(tooth_center, 0)
119
+ .rect(radial_tooth_length, radial_tooth_width)
120
+ .extrude(stator_thickness + 1.0)
 
121
  )
122
+ root_pad = (
123
+ cq.Workplane("XY")
124
+ .center(stator_inner_radius + 2.0, 0)
125
+ .rect(8.0, radial_tooth_width + 4.0)
126
+ .extrude(stator_thickness + 1.0)
127
+ )
128
+ return tooth.union(root_pad).rotate((0, 0, 0), (0, 0, 1), angle)
129
+
130
+ def make_twelve_slot_tooth_set():
131
+ teeth = None
132
+ for tooth_index in range(stator_slot_count):
133
+ radial_tooth = make_radial_tooth(tooth_index)
134
+ teeth = radial_tooth if teeth is None else teeth.union(radial_tooth)
135
+ return teeth
136
+
137
+ stator_ring = make_stator_ring()
138
+ twelve_radial_teeth = make_twelve_slot_tooth_set()
139
+ center_shaft_opening = cq.Workplane("XY").circle(stator_shaft_radius).extrude(stator_thickness + 4).translate((0, 0, -2))
140
+ fixture = stator_ring.union(twelve_radial_teeth).cut(center_shaft_opening).clean()
141
  '''.strip(),
142
  },
143
  }
 
300
  <div class="viewer-head">
301
  <div>
302
  <h2 id="viewerTitle">Buildable CAD preview</h2>
303
+ <p id="viewerStatus">Choose a task. CADForge will score a weak seed, repair it, verify it, and render the improved STL.</p>
304
  </div>
305
  <div class="demo-controls">
306
  <select id="taskSelect">
 
312
  </div>
313
  <div id="viewer"></div>
314
  <div class="trace-strip" id="traceStrip">
315
+ <div class="trace-item"><strong>Step 0</strong><span>Waiting for weak seed.</span></div>
316
  <div class="trace-item"><strong>Step 1</strong><span>Waiting for repaired CAD.</span></div>
317
  </div>
318
  <div class="viewer-foot">
 
379
  const viewer = document.querySelector("#viewer");
380
  const scene = new THREE.Scene();
381
  scene.background = new THREE.Color(0xeff4f7);
382
+ const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100000);
383
  camera.position.set(180, -220, 170);
384
  const renderer = new THREE.WebGLRenderer({ antialias: true });
385
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
 
411
  box.getSize(size);
412
  box.getCenter(center);
413
  const maxDim = Math.max(size.x, size.y, size.z, 1);
414
+ camera.near = Math.max(0.1, maxDim / 5000);
415
+ camera.far = Math.max(5000, maxDim * 10);
416
  camera.position.set(center.x + maxDim * 1.25, center.y - maxDim * 1.55, center.z + maxDim * 1.05);
417
+ camera.updateProjectionMatrix();
418
  controls.target.copy(center);
419
  controls.update();
420
  }
421
 
422
  async function runDemo() {
423
  const taskId = document.querySelector("#taskSelect").value;
424
+ document.querySelector("#viewerStatus").textContent = "Running weak seed, repair, CadQuery build, and reward...";
425
  document.querySelector("#traceStrip").innerHTML = `
426
+ <div class="trace-item"><strong>Step 0</strong><span>Evaluating weak seed...</span></div>
427
  <div class="trace-item"><strong>Step 1</strong><span>Waiting for repaired CAD...</span></div>
428
  `;
429
  const response = await fetch("/api/space/repair-loop", {
 
447
  document.querySelector("#semanticMetric").textContent = Number(payload.final.reward.semantic_parts || 0).toFixed(3);
448
  document.querySelector("#rewardJson").textContent = JSON.stringify(payload, null, 2);
449
 
450
+ await renderMeshes(payload, taskId);
451
+ }
452
+
453
+ async function renderMeshes(payload, taskId) {
454
+ if (mesh) {
455
+ scene.remove(mesh);
456
+ mesh = null;
457
+ }
458
+ const group = new THREE.Group();
459
+ const loader = new STLLoader();
460
+ if (payload.seed && payload.seed.stl_url && payload.steps && payload.steps[0] && payload.steps[0].build > 0) {
461
+ const seedGeometry = await loader.loadAsync(payload.seed.stl_url + "?t=" + Date.now());
462
+ seedGeometry.computeVertexNormals();
463
+ const seedMesh = new THREE.Mesh(
464
+ seedGeometry,
465
+ new THREE.MeshStandardMaterial({ color: 0xd65b5b, roughness: 0.72, metalness: 0.20, transparent: true, opacity: 0.24 })
466
+ );
467
+ seedMesh.position.x -= 0.015;
468
+ group.add(seedMesh);
469
+ }
470
+ const finalGeometry = await loader.loadAsync(payload.final.stl_url + "?t=" + Date.now());
471
+ finalGeometry.computeVertexNormals();
472
+ const finalMesh = new THREE.Mesh(
473
+ finalGeometry,
474
+ new THREE.MeshStandardMaterial({ color: taskId.includes("stator") ? 0x2f80ed : 0x2a9d8f, roughness: 0.48, metalness: 0.50 })
475
  );
476
+ group.add(finalMesh);
477
+ mesh = group;
478
  scene.add(mesh);
479
  frameObject(mesh);
480
  }
 
630
  artifact_dir = Path(str(repaired.get("artifacts_dir") or _loop_artifact_dir(task_id, "repaired")))
631
  stl_path = _stl_path(artifact_dir)
632
  steps = [
633
+ _step_payload("weak seed", broken, "CADForge scores the weak first attempt before repair."),
634
  _step_payload("repaired CAD", repaired, "The repaired candidate is rebuilt and rescored."),
635
  ]
636
  if not repaired.get("ok") or not stl_path.exists():
 
655
  "delta_reward": float(repaired_reward.get("total", 0.0) or 0.0)
656
  - float(broken_reward.get("total", 0.0) or 0.0),
657
  "steps": steps,
658
+ "seed": {
659
+ "reward": broken_reward,
660
+ "notes": broken.get("notes", []),
661
+ "elapsed_ms": broken.get("elapsed_ms", 0),
662
+ "stl_url": f"/api/space/loop-stl/{task_id}/broken",
663
+ "artifacts_dir": broken.get("artifacts_dir"),
664
+ },
665
  "final": {
666
  "reward": repaired_reward,
667
  "notes": repaired.get("notes", []),
 
692
  return FileResponse(stl_path, media_type="model/stl", filename=f"{safe_task}-repaired.stl")
693
 
694
 
695
+ @app.get("/api/space/loop-stl/{task_id}/{step_id}")
696
+ def get_space_loop_step_stl(task_id: str, step_id: str) -> FileResponse:
697
+ safe_task = _safe_task_id(task_id)
698
+ safe_step = _safe_task_id(step_id)
699
+ stl_path = _stl_path(_loop_artifact_dir(safe_task, safe_step))
700
+ if not stl_path.exists():
701
+ raise HTTPException(status_code=404, detail="Run the repair loop first.")
702
+ return FileResponse(stl_path, media_type="model/stl", filename=f"{safe_task}-{safe_step}.stl")
703
+
704
+
705
  def main(host: str = "0.0.0.0", port: int = 8000) -> None:
706
  import uvicorn
707