cyclecad 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -947,19 +947,62 @@ export async function executeTextCommand(prompt) {
947
947
 
948
948
  addMessage('ai', `Got it! ${preview}. Creating now...`);
949
949
 
950
- // Execute: try direct geometry creation first (fastest path)
950
+ // ── B-REP ENGINE PATH ──────────────────────────────────────────────
951
+ // Try real OpenCascade.js B-rep first. Falls back to mesh preview.
952
+ const brep = window.brepEngine;
953
+ const brepCommands = commands.map(cmd => {
954
+ const method = cmd.method || '';
955
+ const type = method.replace('shape.', '').replace('feature.', '');
956
+ return { type, params: { ...cmd.params } };
957
+ });
958
+
959
+ // Attempt B-rep execution
960
+ if (brep) {
961
+ try {
962
+ if (!brep.isReady()) {
963
+ addMessage('ai', '⏳ Loading OpenCascade.js B-rep kernel (~50MB WASM)... first time only.');
964
+ await brep.initBRep();
965
+ addMessage('ai', '✅ B-rep kernel loaded!');
966
+ }
967
+
968
+ // Remove previous B-rep mesh from scene
969
+ if (window._brepMesh) {
970
+ if (window._brepMesh.mesh?.parent) window._brepMesh.mesh.parent.remove(window._brepMesh.mesh);
971
+ if (window._brepMesh.wireframe?.parent) window._brepMesh.wireframe.parent.remove(window._brepMesh.wireframe);
972
+ }
973
+
974
+ const result = await brep.executeCommands(brepCommands);
975
+ if (result && result.mesh) {
976
+ // Add to Three.js scene
977
+ const addFn = window.addToScene || ((m) => {
978
+ const scene = window._scene || window.getScene?.();
979
+ if (scene) scene.add(m);
980
+ });
981
+ addFn(result.mesh);
982
+ addFn(result.wireframe);
983
+ window._brepMesh = result;
984
+
985
+ addMessage('ai', `🔧 **Real B-rep**: ${result.description} — solid model with true edges and faces.`);
986
+ return { ok: true, brep: true, description: result.description };
987
+ }
988
+ } catch (brepErr) {
989
+ console.warn('[Copilot] B-rep execution failed, falling back to mesh preview:', brepErr);
990
+ addMessage('ai', `⚠️ B-rep kernel error: ${brepErr.message}. Using mesh preview instead.`);
991
+ }
992
+ }
993
+
994
+ // ── MESH PREVIEW FALLBACK ──────────────────────────────────────────
951
995
  const results = [];
952
996
  for (const cmd of commands) {
953
997
  try {
954
- // Convert Agent API format {method: 'shape.cylinder', params: {}} to executeParsedPrompt format {type: 'cylinder', params: {}}
955
998
  if (window._executeParsedPrompt) {
956
999
  const method = cmd.method || '';
957
1000
  const type = method.replace('shape.', '').replace('feature.', '');
958
1001
 
959
- // Operations that modify existing geometry — skip createPrimitive, show message
1002
+ // Operations that modify existing geometry
960
1003
  const modifyOps = ['fillet', 'chamfer', 'pattern', 'mirror', 'shell'];
961
1004
  if (modifyOps.includes(type)) {
962
- addMessage('ai', `⚡ ${type} applied to selected geometry (visual preview — real B-rep operations coming in Phase A).`);
1005
+ addMessage('ai', `⚡ ${type} install B-rep kernel for real edge operations. Using visual preview.`);
963
1006
  results.push({ ok: true, method, note: 'modify-op' });
964
1007
  continue;
965
1008
  }
@@ -968,23 +1011,15 @@ export async function executeTextCommand(prompt) {
968
1011
  const count = cmd.params?.count || 1;
969
1012
  for (let ci = 0; ci < count; ci++) {
970
1013
  const p = Object.assign({}, cmd.params);
971
- // Position holes at 4 corners of a typical cube face
972
1014
  if (count > 1 && type === 'hole') {
973
- const cornerSpread = 3.5; // scene units — matches ~35mm on a 100mm cube at SCALE 0.1
974
- const corners = [
975
- [-cornerSpread, -cornerSpread],
976
- [ cornerSpread, -cornerSpread],
977
- [ cornerSpread, cornerSpread],
978
- [-cornerSpread, cornerSpread],
979
- ];
980
- const idx = ci % corners.length;
981
- p._offsetX = corners[idx][0];
982
- p._offsetZ = corners[idx][1];
1015
+ const cornerSpread = 3.5;
1016
+ const corners = [[-cornerSpread, -cornerSpread], [cornerSpread, -cornerSpread], [cornerSpread, cornerSpread], [-cornerSpread, cornerSpread]];
1017
+ p._offsetX = corners[ci % 4][0];
1018
+ p._offsetZ = corners[ci % 4][1];
983
1019
  } else if (count > 1) {
984
1020
  const angle = (ci / count) * Math.PI * 2;
985
- const spread = (p.radius || 5) * 3 * 0.1;
986
- p._offsetX = Math.cos(angle) * spread;
987
- p._offsetZ = Math.sin(angle) * spread;
1021
+ p._offsetX = Math.cos(angle) * (p.radius || 5) * 3 * 0.1;
1022
+ p._offsetZ = Math.sin(angle) * (p.radius || 5) * 3 * 0.1;
988
1023
  }
989
1024
  window._executeParsedPrompt({ type, params: p });
990
1025
  }