sanjuhs commited on
Commit
f59dd51
·
verified ·
1 Parent(s): 7d1ed4c

Reload CADForge STL on assembly changes

Browse files
Files changed (1) hide show
  1. server/app.py +85 -21
server/app.py CHANGED
@@ -29,11 +29,9 @@ DEMO_TASKS = {
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
@@ -209,7 +207,7 @@ SPACE_HTML = r'''
209
  }
210
  .viewer-head h2 { margin: 0; font-size: 22px; }
211
  .viewer-head p { margin: 5px 0 0; color: #cee4ec; }
212
- #viewer { min-height: 410px; background: #eff4f7; }
213
  .viewer-foot {
214
  display: grid;
215
  grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -235,6 +233,29 @@ SPACE_HTML = r'''
235
  }
236
  .trace-item strong { display: block; color: #fff; margin-bottom: 4px; }
237
  .trace-item span { color: #aee1ef; font-size: 13px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  .band { padding: 36px 34px; }
239
  .band h2 { font-size: 34px; margin: 0 0 12px; color: #142532; }
240
  .band p { color: #526170; line-height: 1.55; }
@@ -310,7 +331,12 @@ SPACE_HTML = r'''
310
  <button class="light-button" id="runDemo">Run repair loop</button>
311
  </div>
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>
@@ -394,6 +420,7 @@ SPACE_HTML = r'''
394
  grid.rotation.x = Math.PI / 2;
395
  scene.add(grid);
396
  let mesh = null;
 
397
 
398
  function resize() {
399
  const box = viewer.getBoundingClientRect();
@@ -420,23 +447,36 @@ SPACE_HTML = r'''
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", {
430
- method: "POST",
431
- headers: { "content-type": "application/json" },
432
- body: JSON.stringify({ task_id: taskId })
433
- });
434
- const payload = await response.json();
435
- if (!response.ok || !payload.ok) {
436
- document.querySelector("#viewerStatus").textContent = payload.error || "CadQuery build failed.";
437
- if (payload.steps) updateTrace(payload.steps);
438
- document.querySelector("#rewardJson").textContent = JSON.stringify(payload, null, 2);
439
- return;
 
 
 
 
 
 
440
  }
441
  document.querySelector("#viewerTitle").textContent = payload.label;
442
  document.querySelector("#viewerStatus").textContent = "Repair loop complete. Reward delta: " + Number(payload.delta_reward || 0).toFixed(3) + ".";
@@ -447,33 +487,56 @@ SPACE_HTML = r'''
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);
@@ -489,6 +552,7 @@ SPACE_HTML = r'''
489
  }
490
 
491
  document.querySelector("#runDemo").addEventListener("click", runDemo);
 
492
  runDemo();
493
  renderer.setAnimationLoop(() => {
494
  controls.update();
 
29
  "broken_code": r'''
30
  import cadquery as cq
31
 
32
+ # Weak seed: buildable blank plate with almost no requested assembly detail.
33
  plate = cq.Workplane("XY").box(44, 44, 4).translate((0, 0, 2))
34
+ fixture = plate.clean()
 
 
35
  '''.strip(),
36
  "code": r'''
37
  import cadquery as cq
 
207
  }
208
  .viewer-head h2 { margin: 0; font-size: 22px; }
209
  .viewer-head p { margin: 5px 0 0; color: #cee4ec; }
210
+ #viewer { min-height: 410px; background: #eff4f7; position: relative; }
211
  .viewer-foot {
212
  display: grid;
213
  grid-template-columns: repeat(4, minmax(0, 1fr));
 
233
  }
234
  .trace-item strong { display: block; color: #fff; margin-bottom: 4px; }
235
  .trace-item span { color: #aee1ef; font-size: 13px; }
236
+ .viewer-legend {
237
+ position: absolute;
238
+ display: flex;
239
+ gap: 10px;
240
+ left: 14px;
241
+ top: 14px;
242
+ z-index: 2;
243
+ color: #173040;
244
+ font-size: 12px;
245
+ font-weight: 800;
246
+ }
247
+ .viewer-legend span {
248
+ display: inline-flex;
249
+ align-items: center;
250
+ gap: 6px;
251
+ padding: 6px 8px;
252
+ border-radius: 8px;
253
+ background: rgba(255,255,255,0.82);
254
+ box-shadow: 0 8px 24px rgba(16, 39, 54, 0.12);
255
+ }
256
+ .swatch { width: 10px; height: 10px; border-radius: 999px; display: inline-block; }
257
+ .seed-dot { background: #d65b5b; }
258
+ .repair-dot { background: #2a9d8f; }
259
  .band { padding: 36px 34px; }
260
  .band h2 { font-size: 34px; margin: 0 0 12px; color: #142532; }
261
  .band p { color: #526170; line-height: 1.55; }
 
331
  <button class="light-button" id="runDemo">Run repair loop</button>
332
  </div>
333
  </div>
334
+ <div id="viewer">
335
+ <div class="viewer-legend">
336
+ <span><i class="swatch seed-dot"></i> weak seed</span>
337
+ <span><i class="swatch repair-dot"></i> repaired CAD</span>
338
+ </div>
339
+ </div>
340
  <div class="trace-strip" id="traceStrip">
341
  <div class="trace-item"><strong>Step 0</strong><span>Waiting for weak seed.</span></div>
342
  <div class="trace-item"><strong>Step 1</strong><span>Waiting for repaired CAD.</span></div>
 
420
  grid.rotation.x = Math.PI / 2;
421
  scene.add(grid);
422
  let mesh = null;
423
+ let currentRun = 0;
424
 
425
  function resize() {
426
  const box = viewer.getBoundingClientRect();
 
447
  }
448
 
449
  async function runDemo() {
450
+ const runId = ++currentRun;
451
  const taskId = document.querySelector("#taskSelect").value;
452
+ clearViewer();
453
+ document.querySelector("#buildMetric").textContent = "--";
454
+ document.querySelector("#rewardMetric").textContent = "--";
455
+ document.querySelector("#editMetric").textContent = "--";
456
+ document.querySelector("#semanticMetric").textContent = "--";
457
+ document.querySelector("#runDemo").disabled = true;
458
  document.querySelector("#viewerStatus").textContent = "Running weak seed, repair, CadQuery build, and reward...";
459
  document.querySelector("#traceStrip").innerHTML = `
460
  <div class="trace-item"><strong>Step 0</strong><span>Evaluating weak seed...</span></div>
461
  <div class="trace-item"><strong>Step 1</strong><span>Waiting for repaired CAD...</span></div>
462
  `;
463
+ let payload = {};
464
+ try {
465
+ const response = await fetch("/api/space/repair-loop", {
466
+ method: "POST",
467
+ headers: { "content-type": "application/json" },
468
+ body: JSON.stringify({ task_id: taskId })
469
+ });
470
+ payload = await response.json();
471
+ if (runId !== currentRun) return;
472
+ if (!response.ok || !payload.ok) {
473
+ document.querySelector("#viewerStatus").textContent = payload.error || "CadQuery build failed.";
474
+ if (payload.steps) updateTrace(payload.steps);
475
+ document.querySelector("#rewardJson").textContent = JSON.stringify(payload, null, 2);
476
+ return;
477
+ }
478
+ } finally {
479
+ if (runId === currentRun) document.querySelector("#runDemo").disabled = false;
480
  }
481
  document.querySelector("#viewerTitle").textContent = payload.label;
482
  document.querySelector("#viewerStatus").textContent = "Repair loop complete. Reward delta: " + Number(payload.delta_reward || 0).toFixed(3) + ".";
 
487
  document.querySelector("#semanticMetric").textContent = Number(payload.final.reward.semantic_parts || 0).toFixed(3);
488
  document.querySelector("#rewardJson").textContent = JSON.stringify(payload, null, 2);
489
 
490
+ await renderMeshes(payload, taskId, runId);
491
  }
492
 
493
+ function clearViewer() {
494
  if (mesh) {
495
  scene.remove(mesh);
496
+ mesh.traverse((child) => {
497
+ if (child.geometry) child.geometry.dispose();
498
+ if (child.material) child.material.dispose();
499
+ });
500
  mesh = null;
501
  }
502
+ }
503
+
504
+ async function renderMeshes(payload, taskId, runId) {
505
+ clearViewer();
506
  const group = new THREE.Group();
507
  const loader = new STLLoader();
508
+ const geometries = [];
509
  if (payload.seed && payload.seed.stl_url && payload.steps && payload.steps[0] && payload.steps[0].build > 0) {
510
  const seedGeometry = await loader.loadAsync(payload.seed.stl_url + "?t=" + Date.now());
511
+ if (runId !== currentRun) return;
512
  seedGeometry.computeVertexNormals();
513
+ seedGeometry.computeBoundingBox();
514
+ geometries.push(seedGeometry);
515
  const seedMesh = new THREE.Mesh(
516
  seedGeometry,
517
+ new THREE.MeshStandardMaterial({ color: 0xd65b5b, roughness: 0.72, metalness: 0.20, transparent: true, opacity: 0.64 })
518
  );
 
519
  group.add(seedMesh);
520
  }
521
  const finalGeometry = await loader.loadAsync(payload.final.stl_url + "?t=" + Date.now());
522
+ if (runId !== currentRun) return;
523
  finalGeometry.computeVertexNormals();
524
+ finalGeometry.computeBoundingBox();
525
+ geometries.push(finalGeometry);
526
  const finalMesh = new THREE.Mesh(
527
  finalGeometry,
528
  new THREE.MeshStandardMaterial({ color: taskId.includes("stator") ? 0x2f80ed : 0x2a9d8f, roughness: 0.48, metalness: 0.50 })
529
  );
530
  group.add(finalMesh);
531
+ const maxDim = Math.max(...geometries.map((geometry) => {
532
+ const size = new THREE.Vector3();
533
+ geometry.boundingBox.getSize(size);
534
+ return Math.max(size.x, size.y, size.z, 1);
535
+ }));
536
+ group.children.forEach((child, index) => {
537
+ child.geometry.center();
538
+ child.position.x = group.children.length > 1 ? (index === 0 ? -maxDim * 0.68 : maxDim * 0.68) : 0;
539
+ });
540
  mesh = group;
541
  scene.add(mesh);
542
  frameObject(mesh);
 
552
  }
553
 
554
  document.querySelector("#runDemo").addEventListener("click", runDemo);
555
+ document.querySelector("#taskSelect").addEventListener("change", runDemo);
556
  runDemo();
557
  renderer.setAnimationLoop(() => {
558
  controls.update();