cyclecad 3.10.3 → 3.10.4
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.
- package/CLAUDE.md +85 -0
- package/app/js/modules/ai-copilot.js +80 -0
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -1119,3 +1119,88 @@ import { startSketch, endSketch, setTool, getEntities, clearSketch, entitiesToGe
|
|
|
1119
1119
|
|
|
1120
1120
|
# currentDate
|
|
1121
1121
|
Today's date is 2026-04-01.
|
|
1122
|
+
|
|
1123
|
+
## Session 2026-04-23 — AI Copilot Template Library + Real CSG
|
|
1124
|
+
|
|
1125
|
+
### What shipped
|
|
1126
|
+
- **cyclecad@3.10.2** (live on npm): AI Copilot v1.1 with template library, real CSG booleans, draggable dialogs, click-pin menus
|
|
1127
|
+
- **cyclecad@3.10.3** (pending/retry): template expansion with 5 more shapes (washer, flange, threaded rod, mounting plate, box)
|
|
1128
|
+
|
|
1129
|
+
### Key files
|
|
1130
|
+
- `app/js/modules/ai-copilot.js` (~900 lines) — multi-step CAD copilot. IIFE that sets `window.CycleCAD.AICopilot`. Key internals: `matchTemplate()`, `miniExecute()` (async), `subtractFromBody()` (CSG), `loadCSG()` (lazy import vendored lib), `run()` (orchestrator), `buildUI()`
|
|
1131
|
+
- `app/js/vendor/three-bvh-csg.js` (3892 lines) — vendored three-bvh-csg@0.0.17 with bare imports rewritten: `'three'` and `'three-mesh-bvh'` → CDN URLs (`cdn.jsdelivr.net/npm/three@0.170.0/...` and `three-mesh-bvh@0.7.8`)
|
|
1132
|
+
- `app/index.html` — has `<script src="/app/js/modules/ai-copilot.js?v=HASH">` (cache-busted) and menu entry `data-action="tools-ai-copilot"`
|
|
1133
|
+
|
|
1134
|
+
### Template library (8 shapes) — `matchTemplate(prompt)` in ai-copilot.js
|
|
1135
|
+
All templates bypass LLM and output a fixed JSON plan with correct DIN/ISO coordinates:
|
|
1136
|
+
| Prompt pattern | Shape | Notes |
|
|
1137
|
+
|---|---|---|
|
|
1138
|
+
| `raspberry pi \| rpi \| pi 4` + `case` | Pi case | Case body 89×14×60, 4 mounting posts at ±38.75, y=14, ±26. Optional USB/HDMI/Ethernet ops.hole cutouts |
|
|
1139
|
+
| `M(n) nut` | Hex nut (DIN 934) | Cylinder with correct across-flats + thickness by M-size |
|
|
1140
|
+
| `L-bracket` + `Nmm` + `N holes` + `Nmm centers` | L-bracket | Rect plate + N CSG holes |
|
|
1141
|
+
| `M(n) washer` or `DIN 125 M(n)` | Washer | Disk + center hole, DIN 125 spec |
|
|
1142
|
+
| `flange` + `Nmm` + `N bolt holes` + `PCD N` | Flange | Disk + bolt circle + center bore |
|
|
1143
|
+
| `threaded rod` + `M(n)` + `Nmm` | Threaded rod | Cylinder by M-size + length |
|
|
1144
|
+
| `mounting plate` + `NxN` + `N holes` | Mounting plate | Rect + N holes (4-hole = corners, else evenly spaced) |
|
|
1145
|
+
| `box NxNxN` | Generic box | Box extrude |
|
|
1146
|
+
|
|
1147
|
+
### Coordinate system (for mini-executor)
|
|
1148
|
+
- X = left/right, Y = up, Z = front/back
|
|
1149
|
+
- All solids centered at origin: rect W×H → [-W/2,W/2] × [-H/2,H/2]
|
|
1150
|
+
- ops.extrude depth=D → Y spans [0, D]
|
|
1151
|
+
- Params `position:[x,y,z]` places the centerpoint of the extruded solid
|
|
1152
|
+
|
|
1153
|
+
### Mini-executor API (handles these methods)
|
|
1154
|
+
- `sketch.start/rect/circle/line/end`
|
|
1155
|
+
- `ops.extrude {depth, position, subtract}` — subtract:true does CSG cut
|
|
1156
|
+
- `ops.hole {position, depth, radius OR width+height}` — CSG subtraction from body
|
|
1157
|
+
- `ops.revolve/fillet/chamfer/shell/pattern` (fillet/chamfer/shell are visual-approx only)
|
|
1158
|
+
- `view.set/fit`, `query.*`, `validate.*`
|
|
1159
|
+
|
|
1160
|
+
### State (IIFE-local `miniState`)
|
|
1161
|
+
- `miniState.group` — THREE.Group with name 'AICopilotBuild' (wiped on every `run()` via `miniReset()`)
|
|
1162
|
+
- `miniState.body` — first solid mesh; ALL `ops.hole` subtractions target this (not lastMesh)
|
|
1163
|
+
- `miniState.lastMesh` — last added mesh; used by `ops.pattern` for cloning
|
|
1164
|
+
- `miniState.currentSketch` — `{shape, width, height, radius, origin}` consumed by next `ops.extrude`
|
|
1165
|
+
|
|
1166
|
+
### Critical bug patterns fixed this session
|
|
1167
|
+
1. **Single-quote apostrophe in SYSTEM_PROMPT** — `isn't` closed the JS string. Use `is not` or escape.
|
|
1168
|
+
2. **Module not loading** — `window.CycleCAD.AICopilot` was undefined because wrapped import had syntax error. Always `node --check` the file before push.
|
|
1169
|
+
3. **Base64 2-chunk delivery corrupts file** — large files clipboard-split and concatenated via bash can lose the first half. Prefer single `cat > file << 'EOF'` heredoc.
|
|
1170
|
+
4. **Scene accumulates between runs** — `miniReset()` must be called at start of `run()`. This call was dropped twice in earlier patches.
|
|
1171
|
+
5. **ops.hole eating posts instead of body** — `subtractFromLast` targeted whatever was most recently added. Fixed by tracking `miniState.body` separately and calling `subtractFromBody`.
|
|
1172
|
+
6. **Posts landing at origin** — `ops.extrude` was ignoring `params.position` and only reading `sketch.origin`. Fixed to read params first.
|
|
1173
|
+
7. **Bare imports in vendored library** — `'three-mesh-bvh'` also needed rewriting, not just `'three'`.
|
|
1174
|
+
8. **Cmd+C hijacking Copy** — killer-features.js shortcuts had no Shift modifier. Changed to Cmd+Shift+C/K/P/G/T.
|
|
1175
|
+
|
|
1176
|
+
### Click-pin menu (in index.html)
|
|
1177
|
+
CSS: `.menu-item.open .menu-dropdown { display: flex !important; }`. JS initMenuPin() wires click-to-toggle + click-outside-to-close. File still has the `:hover` fallback for quick hover.
|
|
1178
|
+
|
|
1179
|
+
### Dialog drag (in index.html)
|
|
1180
|
+
Pointer events on `#dialog-title` drag the whole dialog container via `position: fixed; left/top` — set once on pointerdown, updated on pointermove.
|
|
1181
|
+
|
|
1182
|
+
### UX niceties
|
|
1183
|
+
- Banner: `Ready: <model>` (green) or `No <provider> key — click the key icon` (red)
|
|
1184
|
+
- Low-credit errors auto-offer "Switch to Gemini (free)" button
|
|
1185
|
+
- Multi-goal prompts: detect `N` quoted strings, run first, log others to "paste one at a time"
|
|
1186
|
+
- Gemini 404 fallback: `gemini-2.0-flash` → `gemini-1.5-flash` → `gemini-1.5-flash-latest`
|
|
1187
|
+
- Cache-bust: `?v=HASH` query param on script tags, auto-bumped by MD5 of mtime
|
|
1188
|
+
|
|
1189
|
+
### Supported models
|
|
1190
|
+
- Claude Sonnet 4.6 (paid, default when anthropic key present)
|
|
1191
|
+
- Claude Haiku 4.5 / Opus 4.6 (paid)
|
|
1192
|
+
- Gemini 2.0 Flash (free, 10 RPM)
|
|
1193
|
+
- Groq Llama 3.3 70B (free, 30 RPM — better for repeat prompts)
|
|
1194
|
+
|
|
1195
|
+
### Pending
|
|
1196
|
+
- Retry `npm publish cyclecad@3.10.3` (earlier attempt interrupted; registry still shows 3.10.2)
|
|
1197
|
+
- Add gear/pulley/shaft templates (Task #7)
|
|
1198
|
+
- Verify ExplodeView redirects
|
|
1199
|
+
- The `block-only-in-chrome` user report — needs repro with actual prompt
|
|
1200
|
+
|
|
1201
|
+
### Collaboration notes
|
|
1202
|
+
- User on Mac, tests in Chrome/Safari private
|
|
1203
|
+
- Git user is `sachin@sachins-MacBook-Air.fritz.box` (not configured globally; harmless warning)
|
|
1204
|
+
- Clipboard delivery via `write_clipboard` — user pastes in Terminal
|
|
1205
|
+
- Verification via Claude-in-Chrome MCP (navigate → execute → screenshot → dom inspect)
|
|
1206
|
+
- Terminal is tier-click, so clipboard write is BLOCKED while Terminal is frontmost — switch to Chrome first
|
|
@@ -511,6 +511,86 @@
|
|
|
511
511
|
plan.push({method:'view.fit', params:{}});
|
|
512
512
|
return plan;
|
|
513
513
|
}
|
|
514
|
+
// Spur gear (simplified — cylinder disc at OD with center bore)
|
|
515
|
+
const gearM = p.match(/\bspur\s*gear\b|\bgear\b/);
|
|
516
|
+
if (gearM) {
|
|
517
|
+
const modM = p.match(/module\s*(\d+(?:\.\d+)?)|\bm\s*=?\s*(\d+(?:\.\d+)?)\b/);
|
|
518
|
+
const teethM = p.match(/(\d+)\s*(?:teeth|tooth)|z\s*=?\s*(\d+)/);
|
|
519
|
+
const mod = modM ? parseFloat(modM[1]||modM[2]) : 2;
|
|
520
|
+
const teeth = teethM ? parseInt(teethM[1]||teethM[2]) : 20;
|
|
521
|
+
const widthM = p.match(/(\d+)\s*mm\s*(?:wide|width|face)/);
|
|
522
|
+
const width = widthM ? parseInt(widthM[1]) : 10;
|
|
523
|
+
const boreM = p.match(/(\d+)\s*mm\s*bore|bore\s*(\d+)/);
|
|
524
|
+
const bore = boreM ? parseInt(boreM[1]||boreM[2]) : 8;
|
|
525
|
+
const pitchDia = mod * teeth;
|
|
526
|
+
const outsideDia = mod * (teeth + 2);
|
|
527
|
+
return [
|
|
528
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
529
|
+
{method:'sketch.circle', params:{radius: outsideDia/2}},
|
|
530
|
+
{method:'ops.extrude', params:{depth: width, position:[0,0,0]}, note:'spur gear blank — m='+mod+', Z='+teeth+', pitch Ø'+pitchDia+', OD Ø'+outsideDia+' (add involute teeth via Sketch tab polyline)'},
|
|
531
|
+
{method:'ops.hole', params:{position:[0, width/2, 0], radius: bore/2, depth: width+2}, note:'center bore Ø'+bore},
|
|
532
|
+
{method:'view.set', params:{view:'iso'}},
|
|
533
|
+
{method:'view.fit', params:{}}
|
|
534
|
+
];
|
|
535
|
+
}
|
|
536
|
+
// Pulley (V-belt, simplified — disc with center bore, groove noted)
|
|
537
|
+
const pulleyM = p.match(/\bpulley\b|\bv-?belt\s*pulley\b|\btiming\s*pulley\b/);
|
|
538
|
+
if (pulleyM) {
|
|
539
|
+
const odM = p.match(/(\d+)\s*mm/);
|
|
540
|
+
const od = odM ? parseInt(odM[1]) : 80;
|
|
541
|
+
const boreM = p.match(/(\d+)\s*mm\s*bore|bore\s*(\d+)/);
|
|
542
|
+
const bore = boreM ? parseInt(boreM[1]||boreM[2]) : 12;
|
|
543
|
+
const widthM = p.match(/(\d+)\s*mm\s*(?:wide|width)/);
|
|
544
|
+
const width = widthM ? parseInt(widthM[1]) : 20;
|
|
545
|
+
return [
|
|
546
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
547
|
+
{method:'sketch.circle', params:{radius: od/2}},
|
|
548
|
+
{method:'ops.extrude', params:{depth: width, position:[0,0,0]}, note:'pulley blank Ø'+od+'x'+width+' (add V-groove via revolve in Solid tab)'},
|
|
549
|
+
{method:'ops.hole', params:{position:[0, width/2, 0], radius: bore/2, depth: width+2}, note:'center bore Ø'+bore},
|
|
550
|
+
{method:'view.set', params:{view:'iso'}},
|
|
551
|
+
{method:'view.fit', params:{}}
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
// Shaft (simple cylinder or stepped if "stepped" keyword)
|
|
555
|
+
const shaftM = p.match(/\bshaft\b|\baxle\b|\bspindle\b/);
|
|
556
|
+
if (shaftM) {
|
|
557
|
+
// Explicit diameter keywords first
|
|
558
|
+
const diaM = p.match(/(\d+)\s*mm\s*(?:dia|diameter)|Ø\s*(\d+)|ø\s*(\d+)/);
|
|
559
|
+
// Explicit length keywords
|
|
560
|
+
const lenM = p.match(/(\d+)\s*mm\s*(?:long|length|tall)/);
|
|
561
|
+
// Fallback: if both missing, heuristic from bare Nmm values (smaller=dia, larger=length)
|
|
562
|
+
const allMm = (p.match(/(\d+)\s*mm/g) || []).map(s => parseInt(s)).filter(n => n > 0);
|
|
563
|
+
let dia, len;
|
|
564
|
+
if (diaM) dia = parseInt(diaM[1]||diaM[2]||diaM[3]);
|
|
565
|
+
if (lenM) len = parseInt(lenM[1]);
|
|
566
|
+
if (!dia && !len && allMm.length === 2) { dia = Math.min(allMm[0], allMm[1]); len = Math.max(allMm[0], allMm[1]); }
|
|
567
|
+
else if (!dia && allMm.length >= 1) dia = allMm[0];
|
|
568
|
+
else if (!len && allMm.length >= 2) len = allMm[1];
|
|
569
|
+
dia = dia || 20; len = len || 100;
|
|
570
|
+
const stepped = /\bstepped\b|\bstep\b|\b2[- ]?step\b/.test(p);
|
|
571
|
+
if (stepped) {
|
|
572
|
+
const dia2 = Math.max(6, dia - 4);
|
|
573
|
+
const len1 = Math.round(len * 0.6);
|
|
574
|
+
const len2 = len - len1;
|
|
575
|
+
return [
|
|
576
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
577
|
+
{method:'sketch.circle', params:{radius: dia/2}},
|
|
578
|
+
{method:'ops.extrude', params:{depth: len1, position:[0,0,0]}, note:'stepped shaft main Ø'+dia+' x '+len1+'mm'},
|
|
579
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
580
|
+
{method:'sketch.circle', params:{radius: dia2/2}},
|
|
581
|
+
{method:'ops.extrude', params:{depth: len2, position:[0,len1,0]}, note:'stepped shaft reduced Ø'+dia2+' x '+len2+'mm'},
|
|
582
|
+
{method:'view.set', params:{view:'iso'}},
|
|
583
|
+
{method:'view.fit', params:{}}
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
return [
|
|
587
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
588
|
+
{method:'sketch.circle', params:{radius: dia/2}},
|
|
589
|
+
{method:'ops.extrude', params:{depth: len, position:[0,0,0]}, note:'shaft Ø'+dia+' x '+len+'mm'},
|
|
590
|
+
{method:'view.set', params:{view:'iso'}},
|
|
591
|
+
{method:'view.fit', params:{}}
|
|
592
|
+
];
|
|
593
|
+
}
|
|
514
594
|
return null;
|
|
515
595
|
}
|
|
516
596
|
async function run(){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cyclecad",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.4",
|
|
4
4
|
"description": "Browser-based parametric 3D CAD modeler with AI-powered tools, native Inventor file parsing, and smart assembly management. No install required.",
|
|
5
5
|
"main": "index.html",
|
|
6
6
|
"bin": {
|