cyclecad 3.10.4 → 3.12.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.
- package/.github/workflows/pages.yml +34 -0
- package/.nojekyll +0 -0
- package/CLAUDE.md +348 -3
- package/HANDOFF-2026-04-24-session-2.md +239 -0
- package/HANDOFF-2026-04-24.md +90 -0
- package/app/index.html +49 -10
- package/app/js/modules/ai-copilot.js +195 -2
- package/app/js/modules/ai-engineer.js +939 -0
- package/app/js/modules/pentacad-bridge.js +216 -0
- package/app/js/modules/pentacad-cam.js +184 -0
- package/app/js/modules/pentacad-sim.js +215 -0
- package/app/js/modules/pentacad.js +233 -0
- package/app/pentacad.html +240 -0
- package/cyclecad.html +1081 -0
- package/explodeview.html +1102 -0
- package/index-agent-first.html.bak +1306 -0
- package/index.html +1683 -1240
- package/machines/v2-50-chb/kinematics.json +51 -0
- package/mockups/cyclecad-suite-mockup.html +1746 -0
- package/package.json +1 -1
- package/pentacad.html +1097 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Handoff — 2026-04-24 session → next chat
|
|
2
|
+
|
|
3
|
+
Drop this file into the next chat (or tell Claude to read it). Covers both cycleCAD and ExplodeView.
|
|
4
|
+
|
|
5
|
+
## Who I am
|
|
6
|
+
- SACHIN (vvlars@googlemail.com, GitHub vvlars-cmd)
|
|
7
|
+
- Two repos: `~/cyclecad` (parametric 3D CAD modeler) + `~/explodeview` (CAD viewer for STEP files)
|
|
8
|
+
- Working style: fast iteration, minimal clarifying questions, terminal-paste commands via clipboard, Safari private window for testing
|
|
9
|
+
- Full memory is in `~/cyclecad/CLAUDE.md` and `~/explodeview/CLAUDE.md` — read those first if you need historical context
|
|
10
|
+
|
|
11
|
+
## What shipped this session
|
|
12
|
+
- **ExplodeView v1.0.22 / v304** live on npm + GitHub Pages
|
|
13
|
+
- Commit `b47d6a9` (code) + `41471f3` (version bump)
|
|
14
|
+
- Rewrote `buildPrompt()` in `docs/demo/app.js` line 6595 with preservation-first logic
|
|
15
|
+
- Added "No Style" preset (`presetSuffixes.none = ''`) + button in `docs/demo/index.html`
|
|
16
|
+
- Cache bump 303 → 304, version badge updated
|
|
17
|
+
- Fixes the "Nano Banana v2 ignores the reference CAD object" problem from the 2026-04-24 diagnosis
|
|
18
|
+
- **cycleCAD@3.10.4** confirmed live on npm (from earlier in the day — gear/pulley/shaft AI Copilot templates)
|
|
19
|
+
- **cycleCAD/CLAUDE.md** updated with the new AI Engineering Analyst roadmap spec (see below)
|
|
20
|
+
|
|
21
|
+
## Top pending item — AI Engineering Analyst (the big one)
|
|
22
|
+
|
|
23
|
+
**Source of inspiration**: MecAgent demo screenshots in `/Users/sachin/Desktop/mec` (12 PNGs from 11:49–11:51). The demo runs inside Autodesk Inventor and solves a complete bolted joint problem in natural language — parses "4×M12 10.9 bolts, 18kN shear, 18kN axial, 420Nm moment, μ=0.16, preload 39kN, BCD 96mm, K_s=1.5", walks through slip/tension/combined-stress checks with equations, cites textbook pages ("Analysis and Design of Machine Elements" by Wei Jiang, Wiley p.85), verdict "safe".
|
|
24
|
+
|
|
25
|
+
**Why this matters**: cycleCAD's existing `ai-copilot.js` builds geometry (gears, bolts, flanges), `fusion-simulation.js` has stubbed FEA, `validate.designReview` returns A-F heuristic scores. None of them answer an engineering question with analytical methods and source citations. This is the biggest competitive gap vs MecAgent and the clearest proof of cycleCAD's "agent-first for manufacturing" positioning.
|
|
26
|
+
|
|
27
|
+
**Target file**: `app/js/modules/ai-engineer.js` (~800–1200 lines)
|
|
28
|
+
|
|
29
|
+
**Scope v1 — bolted joint (VDI 2230 / Shigley)**:
|
|
30
|
+
- Analytical core: pure JS math, no LLM — functions like `checkSlipResistance({z, Q_F, mu, F_shear, K_s})`, `computeMomentLoad({M, r_i})`, `vonMises({sigma, tau})`
|
|
31
|
+
- Input form: bolt grade (4.6–12.9 dropdown with proof/tensile strength table), count, thread size (M3–M36), preload, external loads (F_shear/F_axial/M + BCD), μ, safety factor target
|
|
32
|
+
- Output: stepwise LaTeX equations (KaTeX preferred over MathJax — faster load, smaller bundle), pass/fail badges per check, final verdict
|
|
33
|
+
- Reuses Fastener Wizard bolt patterns + selected-assembly context as implicit inputs
|
|
34
|
+
|
|
35
|
+
**Scope v2 — other machine elements**:
|
|
36
|
+
- Spur/helical gear pair (AGMA bending + pitting stress)
|
|
37
|
+
- Shaft fatigue (Goodman/Soderberg, stress concentration at keyways/shoulders)
|
|
38
|
+
- Rolling bearing L10 life (C/P^n relation, dynamic vs static load)
|
|
39
|
+
- Fillet weld sizing (throat stress, weld leg from applied loads)
|
|
40
|
+
|
|
41
|
+
**RAG / citations**:
|
|
42
|
+
- Let user upload their own machine-element PDF → chunk + embed locally with `@xenova/transformers` MiniLM-L6-v2 → store in IndexedDB
|
|
43
|
+
- Bundled fallback corpus: public-domain machine-element references + legally-redistributable DIN/ISO standard excerpts
|
|
44
|
+
- Every equation gets `{source: "X", page: N}` metadata → rendered as footnote with "Open Document" button (mimics MecAgent UX exactly)
|
|
45
|
+
|
|
46
|
+
**LLM layer**:
|
|
47
|
+
- Same provider stack as `ai-copilot.js` (Claude Sonnet 4.6 paid, Gemini 2.0 Flash free, Groq Llama 3.3 70B free — already wired)
|
|
48
|
+
- Tool-use pattern: LLM parses natural-language problem → emits structured params → calls analytical JS functions → gets numeric result → writes narrative around it. LLM never fabricates numbers.
|
|
49
|
+
|
|
50
|
+
**UI**:
|
|
51
|
+
- New "Engineer" tab in right panel alongside existing Properties / Chat / Guide / Parameters
|
|
52
|
+
- Input form at top, stepwise calculation log in middle, citations panel at bottom
|
|
53
|
+
|
|
54
|
+
## Other pending items (from CLAUDE.md)
|
|
55
|
+
|
|
56
|
+
### cycleCAD
|
|
57
|
+
- More AI Copilot templates in `app/js/modules/ai-copilot.js` `matchTemplate()` — mounting bracket variants, T-slot extrusions (40/40, 80/40), ball/roller bearings, bearing cutouts. Same pattern as the gear/pulley/shaft block in 3.10.4. Would ship as `3.10.5`.
|
|
58
|
+
- Polyline → geometry support in mini-executor (unblocks true involute gear teeth — today's gear template is a blank cylinder)
|
|
59
|
+
- Dynamic version badge in status bar (currently hardcoded `v0.9.0`, should fetch from package.json)
|
|
60
|
+
- Wire splash buttons (New Sketch / Open-Import / Text-to-CAD / Inventor Project) to actually trigger their actions
|
|
61
|
+
- Run `app/test-agent.html` (113 tests, 15 categories) in Chrome and fix failures
|
|
62
|
+
- Text-to-CAD with live preview
|
|
63
|
+
- Photo-to-CAD reverse engineering
|
|
64
|
+
- 138MB STEP import via server-side `server/converter.py` (safer than opencascade.js v293 path)
|
|
65
|
+
- Docker compose local test
|
|
66
|
+
|
|
67
|
+
### ExplodeView
|
|
68
|
+
- **Compositing render** for guaranteed CAD preservation: render 3D with transparent background, send only the cropped background region to Nano Banana, composite the 3D foreground over the generated background in canvas. Bypasses "generative ≠ inpainting" completely. Probably the right follow-up to v304.
|
|
69
|
+
- Run `docs/demo/killer-features-test.html` in Chrome, fix failures
|
|
70
|
+
- The `block-only-in-chrome` user report — needs repro
|
|
71
|
+
|
|
72
|
+
## Critical operational context (don't forget)
|
|
73
|
+
- **Git lock dance**: VM commits leave stale `.git/*.lock` files. Always give ONE combined command: `rm -f ~/REPO/.git/index.lock ~/REPO/.git/HEAD.lock && cd ~/REPO && git add ... && git commit -m "..." && git push origin main`
|
|
74
|
+
- **Clipboard delivery**: Commands go via `mcp__computer-use__write_clipboard` → user pastes in Terminal. Must switch OFF Terminal (make Chrome or other app frontmost) before writing — Terminal is tier-"click" and blocks clipboard writes when it's frontmost.
|
|
75
|
+
- **Egress allowlist**: `explodeview.com` and `cyclecad.com` are NOT on the allowlist. Use `npm registry` (allowed) to verify version bumps. For live-site verification, either ask user to add domain to Settings → Capabilities, or use the Claude-in-Chrome MCP.
|
|
76
|
+
- **Deferred tools**: All tools show as names in the prompt but schemas are loaded on demand via ToolSearch. Load `computer-use` in bulk: `{query: "computer-use", max_results: 30}`. Load `chrome`: `{query: "chrome", max_results: 20}`.
|
|
77
|
+
- **GitHub Pages concurrent-deploy fix** shipped 2026-04-24 in `.github/workflows/pages.yml` with `concurrency: group: pages, cancel-in-progress: true`. If Pages fails with "deployment in progress" again, that's the first check.
|
|
78
|
+
|
|
79
|
+
## Current npm versions
|
|
80
|
+
- cyclecad: **3.10.4** (next bump should be 3.10.5 if AI Copilot templates added, or 3.11.0 if AI Engineering Analyst ships)
|
|
81
|
+
- explodeview: **1.0.22**
|
|
82
|
+
|
|
83
|
+
## Latest commits
|
|
84
|
+
- ExplodeView: `41471f3` v1.0.22 tag, `b47d6a9` v304 code
|
|
85
|
+
- cycleCAD: HEAD has CLAUDE.md modified locally (untracked pentacad/mockups/machines files present — leave alone unless asked)
|
|
86
|
+
|
|
87
|
+
## How to resume
|
|
88
|
+
1. Read `~/cyclecad/CLAUDE.md` and `~/explodeview/CLAUDE.md` for full history
|
|
89
|
+
2. If continuing AI Engineering Analyst: start with `app/js/modules/ai-engineer.js` skeleton — define the analytical functions for bolted joint first (pure math, no LLM), unit-test them against MecAgent's numbers from the screenshots (F_friction = 24960 N, F_max_tensile = 6687.5 N, σ_vm = 558 MPa), THEN wire in KaTeX rendering, THEN LLM layer, THEN RAG
|
|
90
|
+
3. If continuing something else: ask me which of the pending items to prioritize
|
package/app/index.html
CHANGED
|
@@ -1014,6 +1014,7 @@
|
|
|
1014
1014
|
<button class="menu-item-link" data-action="tools-marketplace">Marketplace...</button>
|
|
1015
1015
|
<div class="menu-separator"></div>
|
|
1016
1016
|
<button class="menu-item-link" data-action="tools-ai-copilot">✨ AI Copilot (multi-step)</button>
|
|
1017
|
+
<button class="menu-item-link" data-action="tools-ai-engineer">🔩 AI Engineering Analyst</button>
|
|
1017
1018
|
<button class="menu-item-link" data-action="tools-text-to-cad">Text-to-CAD (AI)</button>
|
|
1018
1019
|
<button class="menu-item-link" data-action="tools-photo-to-cad">Photo-to-CAD</button>
|
|
1019
1020
|
<button class="menu-item-link" data-action="tools-dfm">Manufacturability Check</button>
|
|
@@ -1411,7 +1412,7 @@
|
|
|
1411
1412
|
</div>
|
|
1412
1413
|
<div class="status-item">
|
|
1413
1414
|
<span class="status-label">Version:</span>
|
|
1414
|
-
<span class="status-value">
|
|
1415
|
+
<span class="status-value" id="status-version">v3.12.0</span>
|
|
1415
1416
|
</div>
|
|
1416
1417
|
</div>
|
|
1417
1418
|
|
|
@@ -1454,12 +1455,15 @@ window._dismissSplash = function(action) {
|
|
|
1454
1455
|
<script>
|
|
1455
1456
|
(function() {
|
|
1456
1457
|
function dismiss() { document.getElementById("welcome-panel").style.display = "none"; }
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
document.getElementById("splash-
|
|
1461
|
-
document.getElementById("splash-
|
|
1462
|
-
document.getElementById("splash-
|
|
1458
|
+
// Queue the action on the pending hook so the main module picks it up after load.
|
|
1459
|
+
// If the module is already loaded, the defineProperty setter dispatches immediately with a 100ms delay.
|
|
1460
|
+
function dispatchAction(action) { dismiss(); window._pendingSplashAction = action; }
|
|
1461
|
+
document.getElementById("splash-sketch") .addEventListener("click", function() { dispatchAction('sketch-new'); });
|
|
1462
|
+
document.getElementById("splash-import") .addEventListener("click", function() { dispatchAction('file-import'); });
|
|
1463
|
+
document.getElementById("splash-textcad") .addEventListener("click", function() { dispatchAction('tools-text-to-cad'); });
|
|
1464
|
+
document.getElementById("splash-imagecad").addEventListener("click", function() { dispatchAction('tools-image-to-cad'); });
|
|
1465
|
+
document.getElementById("splash-openscad").addEventListener("click", function() { dispatchAction('tools-openscad'); });
|
|
1466
|
+
document.getElementById("splash-inventor").addEventListener("click", function() { dispatchAction('file-import'); });
|
|
1463
1467
|
})();
|
|
1464
1468
|
</script>
|
|
1465
1469
|
|
|
@@ -1539,6 +1543,7 @@ window._dismissSplash = function(action) {
|
|
|
1539
1543
|
<!-- Killer Feature Modules -->
|
|
1540
1544
|
<script src="/app/js/agent-api.js?v=a41b98a5"></script>
|
|
1541
1545
|
<script src="/app/js/modules/ai-copilot.js?v=14e5d5d7"></script>
|
|
1546
|
+
<script src="/app/js/modules/ai-engineer.js?v=20260424v1"></script>
|
|
1542
1547
|
<script src="/app/js/modules/text-to-cad.js"></script>
|
|
1543
1548
|
<script src="/app/js/modules/photo-to-cad.js"></script>
|
|
1544
1549
|
<script src="/app/js/modules/manufacturability.js"></script>
|
|
@@ -1957,9 +1962,10 @@ window._dismissSplash = function(action) {
|
|
|
1957
1962
|
})();
|
|
1958
1963
|
}
|
|
1959
1964
|
break;
|
|
1960
|
-
case 'help-about':
|
|
1961
|
-
|
|
1962
|
-
|
|
1965
|
+
case 'help-about': {
|
|
1966
|
+
const v = (window.CycleCAD && window.CycleCAD.version) ? window.CycleCAD.version : '3.12.0';
|
|
1967
|
+
showDialog('About cycleCAD', 'cycleCAD v' + v + '<br>Part of the cycleCAD Suite — parametric CAD, ExplodeView, Pentacad.<br><br>Open-source (MIT) parametric 3D CAD modeller. Built with Three.js, OpenCascade.js for real B-rep, supporting STEP / IGES / GLB import, full parametric modelling, and AI-powered design assistance via the AI Copilot + AI Engineering Analyst.');
|
|
1968
|
+
break; }
|
|
1963
1969
|
case 'tools-ai-copilot':
|
|
1964
1970
|
if (window.CycleCAD && window.CycleCAD.AICopilot) {
|
|
1965
1971
|
showDialog('✨ AI Copilot — multi-step CAD from natural language', '');
|
|
@@ -1967,6 +1973,13 @@ window._dismissSplash = function(action) {
|
|
|
1967
1973
|
if (dbc) { dbc.innerHTML = ''; dbc.appendChild(window.CycleCAD.AICopilot.getUI()); dbc.style.maxHeight = '620px'; dbc.style.overflow = 'auto'; }
|
|
1968
1974
|
} else { showToast('AI Copilot module not loaded', 'error'); }
|
|
1969
1975
|
break;
|
|
1976
|
+
case 'tools-ai-engineer':
|
|
1977
|
+
if (window.CycleCAD && window.CycleCAD.AIEngineer) {
|
|
1978
|
+
showDialog('🔩 AI Engineering Analyst — bolted-joint analysis (VDI 2230 / Shigley)', '');
|
|
1979
|
+
const dbe = document.getElementById('dialog-body');
|
|
1980
|
+
if (dbe) { dbe.innerHTML = ''; dbe.appendChild(window.CycleCAD.AIEngineer.getUI()); dbe.style.maxHeight = '680px'; dbe.style.overflow = 'auto'; }
|
|
1981
|
+
} else { showToast('AI Engineering Analyst module not loaded', 'error'); }
|
|
1982
|
+
break;
|
|
1970
1983
|
case 'tools-text-to-cad':
|
|
1971
1984
|
if (window.CycleCAD && window.CycleCAD.TextToCAD) {
|
|
1972
1985
|
showDialog('Text-to-CAD (AI)', '');
|
|
@@ -2944,6 +2957,32 @@ window._dismissSplash = function(action) {
|
|
|
2944
2957
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initDialogDrag);
|
|
2945
2958
|
else initDialogDrag();
|
|
2946
2959
|
})();
|
|
2960
|
+
|
|
2961
|
+
// ─── Dynamic version badge — fetch from /package.json on load, fall back to embedded ───
|
|
2962
|
+
(function(){
|
|
2963
|
+
const FALLBACK_VERSION = '3.12.0';
|
|
2964
|
+
async function updateVersion() {
|
|
2965
|
+
const badge = document.getElementById('status-version');
|
|
2966
|
+
if (!badge) return;
|
|
2967
|
+
try {
|
|
2968
|
+
const r = await fetch('/package.json', { cache: 'no-cache' });
|
|
2969
|
+
if (!r.ok) throw new Error('package.json HTTP ' + r.status);
|
|
2970
|
+
const pkg = await r.json();
|
|
2971
|
+
if (pkg && pkg.version) {
|
|
2972
|
+
badge.textContent = 'v' + pkg.version;
|
|
2973
|
+
window.CycleCAD = window.CycleCAD || {};
|
|
2974
|
+
window.CycleCAD.version = pkg.version;
|
|
2975
|
+
}
|
|
2976
|
+
} catch (err) {
|
|
2977
|
+
// Keep hardcoded fallback; log quietly for debugging.
|
|
2978
|
+
badge.textContent = 'v' + FALLBACK_VERSION;
|
|
2979
|
+
window.CycleCAD = window.CycleCAD || {};
|
|
2980
|
+
window.CycleCAD.version = FALLBACK_VERSION;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', updateVersion);
|
|
2984
|
+
else updateVersion();
|
|
2985
|
+
})();
|
|
2947
2986
|
</script>
|
|
2948
2987
|
</body>
|
|
2949
2988
|
</html>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* AI Copilot v1.
|
|
1
|
+
/* AI Copilot v1.2 — multi-step CAD generation from natural language. 3.10.5 adds sketch.polyline support + templates for ball bearings (ISO 15/DIN 625), bearing housings, T-slot aluminum extrusions (2020–8080), and U-brackets. */
|
|
2
2
|
(function(){
|
|
3
3
|
'use strict';
|
|
4
4
|
window.CycleCAD = window.CycleCAD || {};
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
'- sketch.start {plane:"XY"}',
|
|
94
94
|
'- sketch.rect {width, height} — rectangle centered at current origin',
|
|
95
95
|
'- sketch.circle {radius} — OR diameter',
|
|
96
|
+
'- sketch.polyline {points:[[x,z],[x,z],...]} — closed polygon in XZ plane (for custom shapes, gear teeth, T-slot profiles, cams)',
|
|
96
97
|
'- sketch.end',
|
|
97
98
|
'- ops.extrude {depth, position:[x,y,z], subtract:bool} — create solid. subtract:true carves from last body.',
|
|
98
99
|
'- ops.hole {position:[x,y,z], depth, radius OR width+height} — carves cylinder OR rectangular hole through last body.',
|
|
@@ -255,6 +256,14 @@
|
|
|
255
256
|
if (method === 'sketch.start') { miniState.currentSketch = { plane: params.plane||'XY', origin: getPos(params) }; return {ok:true}; }
|
|
256
257
|
if (method === 'sketch.rect') { miniState.currentSketch = Object.assign(miniState.currentSketch||{}, { shape:'rect', width: params.width||params.w||50, height: params.height||params.h||30, origin: getPos(params) }); return {ok:true}; }
|
|
257
258
|
if (method === 'sketch.circle'){ miniState.currentSketch = Object.assign(miniState.currentSketch||{}, { shape:'circle', radius: params.radius||params.r||(params.diameter?params.diameter/2:25), origin: getPos(params) }); return {ok:true}; }
|
|
259
|
+
if (method === 'sketch.polyline' || method === 'sketch.polygon') {
|
|
260
|
+
// Points arrive as [[x,z],[x,z],...] in world XZ plane. Min 3 pts for a valid polygon.
|
|
261
|
+
const raw = Array.isArray(params.points) ? params.points : [];
|
|
262
|
+
const pts = raw.filter(p => Array.isArray(p) && p.length >= 2).map(p => [Number(p[0])||0, Number(p[1])||0]);
|
|
263
|
+
if (pts.length < 3) return {ok:false, note:'polyline needs at least 3 points'};
|
|
264
|
+
miniState.currentSketch = Object.assign(miniState.currentSketch||{}, { shape:'polyline', points: pts, origin: getPos(params) });
|
|
265
|
+
return {ok:true};
|
|
266
|
+
}
|
|
258
267
|
if (method === 'sketch.line' || method === 'sketch.end') return {ok:true};
|
|
259
268
|
if (method === 'ops.extrude') {
|
|
260
269
|
const d = params.depth||params.height||params.distance||20;
|
|
@@ -264,6 +273,20 @@
|
|
|
264
273
|
let g;
|
|
265
274
|
if (sk.shape==='rect') g = new THREE.BoxGeometry(sk.width, d, sk.height);
|
|
266
275
|
else if (sk.shape==='circle') g = new THREE.CylinderGeometry(sk.radius, sk.radius, d, 48);
|
|
276
|
+
else if (sk.shape==='polyline' && Array.isArray(sk.points) && sk.points.length >= 3) {
|
|
277
|
+
// Build Shape from [x,z] points. RotateX(-PI/2) maps:
|
|
278
|
+
// Shape-X -> world X, Shape-Y -> world -Z, extrude Z -> world +Y.
|
|
279
|
+
// To preserve user's intent that points[i][1] is world Z, negate Y into Shape.
|
|
280
|
+
const shape = new THREE.Shape();
|
|
281
|
+
const pts = sk.points;
|
|
282
|
+
shape.moveTo(pts[0][0], -pts[0][1]);
|
|
283
|
+
for (let i = 1; i < pts.length; i++) shape.lineTo(pts[i][0], -pts[i][1]);
|
|
284
|
+
shape.closePath();
|
|
285
|
+
g = new THREE.ExtrudeGeometry(shape, { depth: d, bevelEnabled: false, curveSegments: 24 });
|
|
286
|
+
g.translate(0, 0, -d/2); // center along extrude axis BEFORE rotating
|
|
287
|
+
g.rotateX(-Math.PI/2);
|
|
288
|
+
// After rotation: geometry spans world Y:[-d/2, d/2] centered at origin. Matches BoxGeometry/CylinderGeometry convention.
|
|
289
|
+
}
|
|
267
290
|
else g = new THREE.BoxGeometry(50, d, 30);
|
|
268
291
|
const isSubtract = params.subtract === true || params.operation === 'cut' || params.operation === 'subtract';
|
|
269
292
|
const mat = new THREE.MeshStandardMaterial({color: isSubtract?0x1a1a1a:0x4a90e2, metalness:0.35, roughness:0.45});
|
|
@@ -591,6 +614,176 @@
|
|
|
591
614
|
{method:'view.fit', params:{}}
|
|
592
615
|
];
|
|
593
616
|
}
|
|
617
|
+
// Ball bearing (ISO 15 / DIN 625 deep-groove) — simplified solid ring.
|
|
618
|
+
// Designations: 60x (light), 62x (medium), 63x (heavy), 6x (small single-digit), 625, 608 etc.
|
|
619
|
+
const bearingDesignations = {
|
|
620
|
+
'608': {d:8, D:22, B:7},
|
|
621
|
+
'625': {d:5, D:16, B:5},
|
|
622
|
+
'6000': {d:10, D:26, B:8},
|
|
623
|
+
'6001': {d:12, D:28, B:8},
|
|
624
|
+
'6002': {d:15, D:32, B:9},
|
|
625
|
+
'6003': {d:17, D:35, B:10},
|
|
626
|
+
'6004': {d:20, D:42, B:12},
|
|
627
|
+
'6005': {d:25, D:47, B:12},
|
|
628
|
+
'6006': {d:30, D:55, B:13},
|
|
629
|
+
'6200': {d:10, D:30, B:9},
|
|
630
|
+
'6201': {d:12, D:32, B:10},
|
|
631
|
+
'6202': {d:15, D:35, B:11},
|
|
632
|
+
'6203': {d:17, D:40, B:12},
|
|
633
|
+
'6204': {d:20, D:47, B:14},
|
|
634
|
+
'6205': {d:25, D:52, B:15},
|
|
635
|
+
'6206': {d:30, D:62, B:16},
|
|
636
|
+
'6300': {d:10, D:35, B:11},
|
|
637
|
+
'6301': {d:12, D:37, B:12},
|
|
638
|
+
'6302': {d:15, D:42, B:13},
|
|
639
|
+
'6303': {d:17, D:47, B:14},
|
|
640
|
+
'6304': {d:20, D:52, B:15}
|
|
641
|
+
};
|
|
642
|
+
const bearingCodeM = p.match(/\b(6[023]0[0-6]|608|625)\b/);
|
|
643
|
+
// Housing/pocket/seat keywords take priority — a "bearing pocket for 6204" is a housing, not a bearing body.
|
|
644
|
+
const isHousingIntent = /bearing\s*(?:housing|pocket|seat|recess|mount)/.test(p);
|
|
645
|
+
if (bearingCodeM && /bearing/.test(p) && !isHousingIntent) {
|
|
646
|
+
const spec = bearingDesignations[bearingCodeM[1]];
|
|
647
|
+
if (spec) {
|
|
648
|
+
const code = bearingCodeM[1];
|
|
649
|
+
return [
|
|
650
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
651
|
+
{method:'sketch.circle', params:{radius: spec.D/2}},
|
|
652
|
+
{method:'ops.extrude', params:{depth: spec.B, position:[0,0,0]}, note:'bearing '+code+' OD Ø'+spec.D+' x width '+spec.B},
|
|
653
|
+
{method:'ops.hole', params:{position:[0,0,0], radius: spec.d/2, depth: spec.B + 2}, note:code+' bore Ø'+spec.d+' (ISO 15 — simplified ring, real bearing has balls + races)'},
|
|
654
|
+
{method:'view.set', params:{view:'iso'}},
|
|
655
|
+
{method:'view.fit', params:{}}
|
|
656
|
+
];
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// Generic ball bearing with explicit bore ("ball bearing 10mm bore")
|
|
660
|
+
const genBearingM = p.match(/(?:ball|deep[- ]?groove|roller)\s*bearing/);
|
|
661
|
+
if (genBearingM) {
|
|
662
|
+
const boreM = p.match(/(\d+)\s*mm\s*bore|bore\s+(\d+)|Ø\s*(\d+)/);
|
|
663
|
+
const bore = boreM ? parseInt(boreM[1]||boreM[2]||boreM[3]) : 10;
|
|
664
|
+
// Rough approximation matching 62xx series: OD ≈ 2.5*bore + 10, width ≈ 0.4*bore + 4
|
|
665
|
+
const OD = Math.round(bore * 2.5 + 10);
|
|
666
|
+
const B = Math.max(5, Math.round(bore * 0.4 + 4));
|
|
667
|
+
return [
|
|
668
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
669
|
+
{method:'sketch.circle', params:{radius: OD/2}},
|
|
670
|
+
{method:'ops.extrude', params:{depth: B, position:[0,0,0]}, note:'ball bearing Ø'+OD+' x '+B+'mm (approx 62xx series)'},
|
|
671
|
+
{method:'ops.hole', params:{position:[0,0,0], radius: bore/2, depth: B+2}, note:'bore Ø'+bore+' (look up ISO 15 for exact OD)'},
|
|
672
|
+
{method:'view.set', params:{view:'iso'}},
|
|
673
|
+
{method:'view.fit', params:{}}
|
|
674
|
+
];
|
|
675
|
+
}
|
|
676
|
+
// Bearing housing / pocket — block with recess and clearance through-hole
|
|
677
|
+
if (/bearing\s*(?:housing|pocket|seat|recess|mount)/.test(p)) {
|
|
678
|
+
const odM = p.match(/(\d+)\s*mm\s*(?:od|outer|outside)|for\s+(?:a\s+)?(\d+)\s*mm|bearing\s+(\d+)\s*mm/);
|
|
679
|
+
// If the user named a bearing designation, look up its OD
|
|
680
|
+
const housingBearingCode = p.match(/\b(6[023]0[0-6]|608|625)\b/);
|
|
681
|
+
const codeSpec = housingBearingCode ? bearingDesignations[housingBearingCode[1]] : null;
|
|
682
|
+
const OD = odM ? parseInt(odM[1]||odM[2]||odM[3]) : (codeSpec ? codeSpec.D : 32);
|
|
683
|
+
const pocketDepth = Math.max(6, Math.round(OD * 0.3));
|
|
684
|
+
const wall = 8;
|
|
685
|
+
const blockW = OD + 2*wall;
|
|
686
|
+
const blockH = OD + 2*wall;
|
|
687
|
+
const blockD = pocketDepth + 5;
|
|
688
|
+
const throughR = Math.max(3, OD/2 - 4);
|
|
689
|
+
return [
|
|
690
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
691
|
+
{method:'sketch.rect', params:{width: blockW, height: blockH}},
|
|
692
|
+
{method:'ops.extrude', params:{depth: blockD, position:[0,0,0]}, note:'housing block '+blockW+'×'+blockH+'×'+blockD+' for Ø'+OD+' bearing'},
|
|
693
|
+
{method:'ops.hole', params:{position:[0, blockD - pocketDepth, 0], radius: OD/2, depth: pocketDepth + 0.5}, note:'bearing pocket Ø'+OD+' × '+pocketDepth+'mm deep'},
|
|
694
|
+
{method:'ops.hole', params:{position:[0,0,0], radius: throughR, depth: blockD + 2}, note:'shaft clearance bore Ø'+(throughR*2).toFixed(0)},
|
|
695
|
+
// 4 mounting holes at corners
|
|
696
|
+
{method:'ops.hole', params:{position:[-blockW/2 + 6, 0, -blockH/2 + 6], radius: 2.5, depth: blockD + 2}, note:'mounting hole 1/4'},
|
|
697
|
+
{method:'ops.hole', params:{position:[ blockW/2 - 6, 0, -blockH/2 + 6], radius: 2.5, depth: blockD + 2}, note:'mounting hole 2/4'},
|
|
698
|
+
{method:'ops.hole', params:{position:[-blockW/2 + 6, 0, blockH/2 - 6], radius: 2.5, depth: blockD + 2}, note:'mounting hole 3/4'},
|
|
699
|
+
{method:'ops.hole', params:{position:[ blockW/2 - 6, 0, blockH/2 - 6], radius: 2.5, depth: blockD + 2}, note:'mounting hole 4/4'},
|
|
700
|
+
{method:'view.set', params:{view:'iso'}},
|
|
701
|
+
{method:'view.fit', params:{}}
|
|
702
|
+
];
|
|
703
|
+
}
|
|
704
|
+
// T-slot aluminum extrusion (2020/3030/4040/4080 profiles — Bosch/Item/80/20 style)
|
|
705
|
+
const tSlotKw = /\bt-?slot|aluminum\s*(?:extrusion|profile)|\b80[- ]?20\b|\bitem\s*profile|\bmisumi\s*extrusion|\bbosch\s*extrusion|structural\s*extrusion/.test(p);
|
|
706
|
+
const codeM = p.match(/\b(20|25|30|40|45|50|60|80|100)\s*(?:x|×)\s*(20|25|30|40|45|50|60|80|100)\b/);
|
|
707
|
+
const fourDigitM = p.match(/\b(2020|2040|2080|3030|3060|4040|4080|4545|6060|8080)\b/);
|
|
708
|
+
if (tSlotKw || codeM || (fourDigitM && /extrusion|profile|t-?slot/.test(p))) {
|
|
709
|
+
let W = 40, H = 40;
|
|
710
|
+
if (codeM) { W = parseInt(codeM[1]); H = parseInt(codeM[2]); }
|
|
711
|
+
else if (fourDigitM) {
|
|
712
|
+
const c = fourDigitM[1];
|
|
713
|
+
W = parseInt(c.slice(0, c.length/2));
|
|
714
|
+
H = parseInt(c.slice(c.length/2));
|
|
715
|
+
}
|
|
716
|
+
const lenM = p.match(/(\d{2,5})\s*mm\s*(?:long|length)|length\s+(\d{2,5})|\b(\d{3,4})\s*mm\s+(?:rail|bar|stick|piece)/);
|
|
717
|
+
let length = lenM ? parseInt(lenM[1]||lenM[2]||lenM[3]) : 0;
|
|
718
|
+
if (!length) {
|
|
719
|
+
// Fallback: take the largest \d+mm value that isn't the profile code itself (e.g. "4040 extrusion 1000mm")
|
|
720
|
+
const allMm = [...p.matchAll(/\b(\d{2,5})\s*mm\b/g)].map(m => parseInt(m[1]))
|
|
721
|
+
.filter(n => n !== W && n !== H && n !== W*100 + H && n !== W*10 + H);
|
|
722
|
+
length = allMm.length ? Math.max(...allMm) : 500;
|
|
723
|
+
}
|
|
724
|
+
// Slot geometry scales with profile size — approximation of real T-slot
|
|
725
|
+
const slotW = W <= 25 ? 6 : W <= 40 ? 8 : 10; // slot opening (across inward axis)
|
|
726
|
+
const slotD = Math.max(6, W/3); // slot depth
|
|
727
|
+
const yMargin = 2; // covers Y range [0, length]
|
|
728
|
+
const plan = [
|
|
729
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
730
|
+
{method:'sketch.rect', params:{width: W, height: H}},
|
|
731
|
+
{method:'ops.extrude', params:{depth: length, position:[0,0,0]}, note:W+'×'+H+' T-slot extrusion, '+length+'mm long (simplified — real profile has T-grooves + hollow center)'},
|
|
732
|
+
// +X face slot
|
|
733
|
+
{method:'ops.hole', params:{position:[W/2 - slotD/2, 0, 0], width: slotD, height: slotW, depth: length + yMargin}, note:'+X T-slot'},
|
|
734
|
+
// -X face slot
|
|
735
|
+
{method:'ops.hole', params:{position:[-W/2 + slotD/2, 0, 0], width: slotD, height: slotW, depth: length + yMargin}, note:'-X T-slot'},
|
|
736
|
+
// +Z face slot
|
|
737
|
+
{method:'ops.hole', params:{position:[0, 0, H/2 - slotD/2], width: slotW, height: slotD, depth: length + yMargin}, note:'+Z T-slot'},
|
|
738
|
+
// -Z face slot
|
|
739
|
+
{method:'ops.hole', params:{position:[0, 0, -H/2 + slotD/2], width: slotW, height: slotD, depth: length + yMargin}, note:'-Z T-slot'},
|
|
740
|
+
{method:'view.set', params:{view:'iso'}},
|
|
741
|
+
{method:'view.fit', params:{}}
|
|
742
|
+
];
|
|
743
|
+
// For double-wide profiles (e.g. 4080), the longer face gets two T-slots per side
|
|
744
|
+
if (Math.max(W, H) >= 2 * Math.min(W, H)) {
|
|
745
|
+
const longSide = W > H ? 'x' : 'z';
|
|
746
|
+
const longDim = Math.max(W, H);
|
|
747
|
+
const offset = longDim / 4;
|
|
748
|
+
if (longSide === 'x') {
|
|
749
|
+
// Add extra slots on top/bottom (Z faces) since X-axis is longer
|
|
750
|
+
plan.splice(-2, 0,
|
|
751
|
+
{method:'ops.hole', params:{position:[ offset, 0, H/2 - slotD/2], width: slotW, height: slotD, depth: length + yMargin}, note:'+Z T-slot (extra)'},
|
|
752
|
+
{method:'ops.hole', params:{position:[-offset, 0, H/2 - slotD/2], width: slotW, height: slotD, depth: length + yMargin}, note:'+Z T-slot (extra)'},
|
|
753
|
+
{method:'ops.hole', params:{position:[ offset, 0, -H/2 + slotD/2], width: slotW, height: slotD, depth: length + yMargin}, note:'-Z T-slot (extra)'},
|
|
754
|
+
{method:'ops.hole', params:{position:[-offset, 0, -H/2 + slotD/2], width: slotW, height: slotD, depth: length + yMargin}, note:'-Z T-slot (extra)'}
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return plan;
|
|
759
|
+
}
|
|
760
|
+
// U-bracket / channel bracket
|
|
761
|
+
if (/u-?bracket|u-?channel|channel\s*bracket|mounting\s*channel/.test(p)) {
|
|
762
|
+
const wM = p.match(/(\d+)\s*mm\s*(?:wide|width)|width\s+(\d+)/);
|
|
763
|
+
const width = wM ? parseInt(wM[1]||wM[2]) : 60;
|
|
764
|
+
const hM = p.match(/(\d+)\s*mm\s*(?:high|height|tall)|height\s+(\d+)/);
|
|
765
|
+
const height = hM ? parseInt(hM[1]||hM[2]) : 40;
|
|
766
|
+
const lM = p.match(/(\d+)\s*mm\s*(?:long|length)|length\s+(\d+)/);
|
|
767
|
+
const length = lM ? parseInt(lM[1]||lM[2]) : 100;
|
|
768
|
+
const wall = Math.max(3, Math.round(Math.min(width, height) / 12));
|
|
769
|
+
const holesM = p.match(/(\d+)\s*holes?/);
|
|
770
|
+
const nHoles = holesM ? parseInt(holesM[1]) : 2;
|
|
771
|
+
const plan = [
|
|
772
|
+
{method:'sketch.start', params:{plane:'XY'}},
|
|
773
|
+
{method:'sketch.rect', params:{width: width, height: length}},
|
|
774
|
+
{method:'ops.extrude', params:{depth: height, position:[0,0,0]}, note:'U-bracket blank '+width+'×'+height+'×'+length+'mm'},
|
|
775
|
+
// Pocket cut from top down to wall thickness above base. Final Y span: [wall-0.5, height+1.5], intersects body at [wall, height].
|
|
776
|
+
{method:'ops.hole', params:{position:[0, wall, 0], width: width - 2*wall, height: length + 2, depth: height - wall + 2}, note:'U-bracket pocket ('+(width - 2*wall)+'×'+(height - wall)+' leaves base + 2 side walls)'}
|
|
777
|
+
];
|
|
778
|
+
// Mounting holes through the base plate
|
|
779
|
+
for (let i = 0; i < nHoles; i++) {
|
|
780
|
+
const y = (nHoles === 1) ? 0 : -length/2 + 12 + i * ((length - 24) / Math.max(1, nHoles - 1));
|
|
781
|
+
plan.push({method:'ops.hole', params:{position:[0, 0, y], radius: 3, depth: wall + 2}, note:'base mounting hole '+(i+1)+'/'+nHoles});
|
|
782
|
+
}
|
|
783
|
+
plan.push({method:'view.set', params:{view:'iso'}});
|
|
784
|
+
plan.push({method:'view.fit', params:{}});
|
|
785
|
+
return plan;
|
|
786
|
+
}
|
|
594
787
|
return null;
|
|
595
788
|
}
|
|
596
789
|
async function run(){
|
|
@@ -723,5 +916,5 @@
|
|
|
723
916
|
abort: () => abort(),
|
|
724
917
|
getState: () => ({ running:S.running, stepIndex:S.stepIndex, results:S.results.length, errors:S.errors.length, model:S.els.model?.value })
|
|
725
918
|
};
|
|
726
|
-
console.log('AI Copilot v1.
|
|
919
|
+
console.log('AI Copilot v1.2 module loaded');
|
|
727
920
|
})();
|