cyclecad 3.10.2 → 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/index.html +1 -1
- package/app/js/modules/ai-copilot.js +179 -1
- 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
|
package/app/index.html
CHANGED
|
@@ -1538,7 +1538,7 @@ window._dismissSplash = function(action) {
|
|
|
1538
1538
|
|
|
1539
1539
|
<!-- Killer Feature Modules -->
|
|
1540
1540
|
<script src="/app/js/agent-api.js?v=a41b98a5"></script>
|
|
1541
|
-
<script src="/app/js/modules/ai-copilot.js?v=
|
|
1541
|
+
<script src="/app/js/modules/ai-copilot.js?v=14e5d5d7"></script>
|
|
1542
1542
|
<script src="/app/js/modules/text-to-cad.js"></script>
|
|
1543
1543
|
<script src="/app/js/modules/photo-to-cad.js"></script>
|
|
1544
1544
|
<script src="/app/js/modules/manufacturability.js"></script>
|
|
@@ -390,7 +390,105 @@
|
|
|
390
390
|
{method:'view.fit', params:{}}
|
|
391
391
|
];
|
|
392
392
|
}
|
|
393
|
-
//
|
|
393
|
+
// DIN 125 washer (M3-M12)
|
|
394
|
+
const washerM = p.match(/\bm(\d+)\s*washer|washer\s+m(\d+)|din\s*125\s*m?(\d+)/);
|
|
395
|
+
if (washerM) {
|
|
396
|
+
const size = parseInt(washerM[1]||washerM[2]||washerM[3]);
|
|
397
|
+
const outerR = ({3:3.5, 4:4.5, 5:5.3, 6:6.4, 8:8.4, 10:10.5, 12:13})[size] || size*1.2;
|
|
398
|
+
const thick = ({3:0.5, 4:0.8, 5:1, 6:1.6, 8:1.6, 10:2, 12:2.5})[size] || size*0.2;
|
|
399
|
+
const holeR = (size + 0.4) / 2;
|
|
400
|
+
return [
|
|
401
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
402
|
+
{method:'sketch.circle', params:{radius: outerR}},
|
|
403
|
+
{method:'ops.extrude', params:{depth: thick, position:[0,0,0]}, note:'DIN 125 M'+size+' washer'},
|
|
404
|
+
{method:'ops.hole', params:{position:[0, thick, 0], radius: holeR, depth: thick+2}, note:'M'+size+' hole'},
|
|
405
|
+
{method:'view.set', params:{view:'iso'}},
|
|
406
|
+
{method:'view.fit', params:{}}
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
// Flange with bolt circle
|
|
410
|
+
const flangeM = p.match(/flange/);
|
|
411
|
+
if (flangeM) {
|
|
412
|
+
const odM = p.match(/(\d+)\s*mm/);
|
|
413
|
+
const od = odM ? parseInt(odM[1]) : 80;
|
|
414
|
+
const nM = p.match(/(\d+)\s*(?:bolt\s*)?holes?/);
|
|
415
|
+
const nHoles = nM ? parseInt(nM[1]) : 4;
|
|
416
|
+
const pcdM = p.match(/pcd\s*(\d+)|bolt\s*circle\s*(\d+)/);
|
|
417
|
+
const pcd = pcdM ? parseInt(pcdM[1]||pcdM[2]) : Math.round(od*0.7);
|
|
418
|
+
const thick = 8;
|
|
419
|
+
const plan = [
|
|
420
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
421
|
+
{method:'sketch.circle', params:{radius: od/2}},
|
|
422
|
+
{method:'ops.extrude', params:{depth: thick, position:[0,0,0]}, note:'flange body Ø'+od},
|
|
423
|
+
{method:'ops.hole', params:{position:[0, thick, 0], radius: Math.max(5, od/8), depth: thick+2}, note:'center bore'}
|
|
424
|
+
];
|
|
425
|
+
for (let i = 0; i < nHoles; i++) {
|
|
426
|
+
const a = (i / nHoles) * Math.PI * 2;
|
|
427
|
+
const x = Math.cos(a) * pcd/2, z = Math.sin(a) * pcd/2;
|
|
428
|
+
plan.push({method:'ops.hole', params:{position:[x, thick/2, z], radius: 3, depth: thick+2}, note:'bolt hole '+(i+1)+'/'+nHoles});
|
|
429
|
+
}
|
|
430
|
+
plan.push({method:'view.set', params:{view:'iso'}});
|
|
431
|
+
plan.push({method:'view.fit', params:{}});
|
|
432
|
+
return plan;
|
|
433
|
+
}
|
|
434
|
+
// Threaded rod / stud
|
|
435
|
+
const rodM = p.match(/threaded\s*rod|m(\d+)\s*rod|m(\d+)\s*stud|studding/);
|
|
436
|
+
if (rodM) {
|
|
437
|
+
const sM = p.match(/m(\d+)/);
|
|
438
|
+
const size = sM ? parseInt(sM[1]) : 8;
|
|
439
|
+
const lM = p.match(/(\d+)\s*mm/);
|
|
440
|
+
const len = lM ? parseInt(lM[1]) : 100;
|
|
441
|
+
return [
|
|
442
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
443
|
+
{method:'sketch.circle', params:{radius: size/2}},
|
|
444
|
+
{method:'ops.extrude', params:{depth: len, position:[0,0,0]}, note:'M'+size+' threaded rod, '+len+'mm long'},
|
|
445
|
+
{method:'view.set', params:{view:'iso'}},
|
|
446
|
+
{method:'view.fit', params:{}}
|
|
447
|
+
];
|
|
448
|
+
}
|
|
449
|
+
// Mounting plate
|
|
450
|
+
const plateM = p.match(/mounting\s*plate|base\s*plate|flat\s*plate/);
|
|
451
|
+
if (plateM) {
|
|
452
|
+
const dimM = p.match(/(\d+)\s*x\s*(\d+)/);
|
|
453
|
+
const w = dimM ? parseInt(dimM[1]) : 120;
|
|
454
|
+
const h = dimM ? parseInt(dimM[2]) : 80;
|
|
455
|
+
const thick = 6;
|
|
456
|
+
const nM = p.match(/(\d+)\s*holes?/);
|
|
457
|
+
const nHoles = nM ? parseInt(nM[1]) : 4;
|
|
458
|
+
const plan = [
|
|
459
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
460
|
+
{method:'sketch.rect', params:{width: w, height: h}},
|
|
461
|
+
{method:'ops.extrude', params:{depth: thick, position:[0,0,0]}, note: w+'x'+h+'x'+thick+' mounting plate'}
|
|
462
|
+
];
|
|
463
|
+
if (nHoles === 4) {
|
|
464
|
+
const mx = w/2 - 10, mz = h/2 - 10;
|
|
465
|
+
[[-mx,-mz],[mx,-mz],[-mx,mz],[mx,mz]].forEach((pp,i) => {
|
|
466
|
+
plan.push({method:'ops.hole', params:{position:[pp[0], thick/2, pp[1]], radius:3, depth:thick+2}, note:'corner hole '+(i+1)});
|
|
467
|
+
});
|
|
468
|
+
} else {
|
|
469
|
+
for (let i = 0; i < nHoles; i++) {
|
|
470
|
+
const x = -w/2 + 10 + (i/(nHoles-1)) * (w-20);
|
|
471
|
+
plan.push({method:'ops.hole', params:{position:[x, thick/2, 0], radius:3, depth:thick+2}, note:'hole '+(i+1)});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
plan.push({method:'view.set', params:{view:'iso'}});
|
|
475
|
+
plan.push({method:'view.fit', params:{}});
|
|
476
|
+
return plan;
|
|
477
|
+
}
|
|
478
|
+
// Generic box NxNxN with optional fillet
|
|
479
|
+
const boxM = p.match(/\bbox\b|\bblock\b|\bcube\b|\bcuboid\b/);
|
|
480
|
+
const boxDim = p.match(/(\d+)\s*[x×]\s*(\d+)\s*[x×]\s*(\d+)/);
|
|
481
|
+
if (boxM && boxDim) {
|
|
482
|
+
const w = parseInt(boxDim[1]), h = parseInt(boxDim[2]), d = parseInt(boxDim[3]);
|
|
483
|
+
return [
|
|
484
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
485
|
+
{method:'sketch.rect', params:{width: w, height: d}},
|
|
486
|
+
{method:'ops.extrude', params:{depth: h, position:[0,0,0]}, note: w+'x'+h+'x'+d+' box'},
|
|
487
|
+
{method:'view.set', params:{view:'iso'}},
|
|
488
|
+
{method:'view.fit', params:{}}
|
|
489
|
+
];
|
|
490
|
+
}
|
|
491
|
+
// L-bracket with holes
|
|
394
492
|
if (/l-?bracket|mounting\s*bracket|angle\s*bracket/.test(p)) {
|
|
395
493
|
const lenM = p.match(/(\d+)\s*mm/);
|
|
396
494
|
const length = lenM ? parseInt(lenM[1]) : 100;
|
|
@@ -413,6 +511,86 @@
|
|
|
413
511
|
plan.push({method:'view.fit', params:{}});
|
|
414
512
|
return plan;
|
|
415
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
|
+
}
|
|
416
594
|
return null;
|
|
417
595
|
}
|
|
418
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": {
|