cyclecad 3.9.4 → 3.9.6

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.
Files changed (3) hide show
  1. package/CLAUDE.md +165 -1
  2. package/app/index.html +217 -52
  3. package/package.json +1 -1
package/CLAUDE.md CHANGED
@@ -953,5 +953,169 @@ rm -f ~/explodeview/.git/index.lock ~/explodeview/.git/HEAD.lock && cd ~/explode
953
953
  - [ ] Build Photo-to-CAD Reverse Engineering
954
954
  - [ ] Build Instant Manufacturability Feedback
955
955
 
956
+ ## Session 2026-04-01 — Critical Bug Fixes + Sketch Wiring + cycleWASH Prospects
957
+
958
+ ### Problem Evolution (Chronological)
959
+ 1. **cycleWASH prospect spreadsheet** — Built `cycleWASH-NRW-Prospects.xlsx` with 36 leads (bike shops/rentals in NRW), email templates (DE+EN), follow-up tracker. Created with openpyxl, 3 sheets, color-coded priorities.
960
+ 2. **TextToCAD syntax error** — `app/js/modules/text-to-cad.js` line 215 had missing `(` after `if` in regex test. Fixed, pushed.
961
+ 3. **Splash screen missing** — App launched showing demo geometry (blue box + green cylinder) instead of welcome splash. Built splash with 4 buttons (New Sketch, Open/Import, Text-to-CAD, Inventor Project).
962
+ 4. **Splash buttons not responding (3 attempts)** — Root cause: `<script type="module">` creates its own scope. Functions inside cannot be called from inline HTML `onclick`. Solution: regular `<script>` IIFE with `addEventListener` on `id`-based buttons.
963
+ 5. **VM mount is copy-on-write** — CRITICAL LESSON: Files edited via Edit tool exist only in VM overlay, NOT on user's Mac. `git status` on Mac shows "nothing to commit". Only reliable delivery: `write_clipboard` → user pastes in Terminal.
964
+ 6. **Dialog selectors wrong** — All 12 Tools menu handlers used `.dialog-content` (doesn't exist). Correct selector: `#dialog-body`. Fixed via clipboard Python command.
965
+ 7. **Demo geometry removal** — Removed hardcoded `BoxGeometry(60,40,80)` + `CylinderGeometry(20,20,60)`.
966
+ 8. **ViewCube click-only** — Only had click-to-snap-to-face. Added drag-to-rotate with pointer events (pointerdown/pointermove/pointerup), spherical coordinate rotation of main camera. Enlarged 90px → 120px.
967
+ 9. **Hard Reset button missing** — Added to Help menu (red text). Clears Service Workers, Cache API, localStorage, sessionStorage, IndexedDB. Works in Safari/Chrome/Firefox.
968
+ 10. **GitHub Pages CDN cache** — All fixes deployed to GitHub but Pages CDN served stale version. Added `<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">` to prevent future caching.
969
+ 11. **Sketch tools not wired** — Sketch menu items (Line/Circle/Rectangle/Arc/Polyline) existed in UI but `handleMenuAction` only showed toasts. `sketch.js` module was never imported. Wired all tools with auto-enter sketch mode on XY plane.
970
+ 12. **CRITICAL: Wrong import names killed entire module** — Imported `entitiesToShape` and `getSketchEntities` from `sketch.js`, but actual exports are `entitiesToGeometry` and `getEntities`. This silently crashed the ENTIRE `<script type="module">` block — meaning NO app logic worked (no buttons, no menus, no ViewCube, nothing). Fixed import names.
971
+
972
+ ### Key Technical Discoveries
973
+
974
+ **ES Module Silent Failures:**
975
+ When an `import { wrongName } from './module.js'` fails, the entire `<script type="module">` block dies silently — NO console error in Chrome. The page renders HTML/CSS normally but zero JS functionality works. Diagnosis: check `window._scene` (set by module script) — if undefined, module crashed.
976
+
977
+ **How to diagnose:**
978
+ ```javascript
979
+ // Dynamic import catches the real error
980
+ import('./js/sketch.js').then(m => console.log(Object.keys(m))).catch(e => console.error(e));
981
+ ```
982
+
983
+ **sketch.js actual exports (v0.9.0):**
984
+ `canvasToWorld, clearSketch, endSketch, entitiesToGeometry, getEntities, setGridSize, setSnapEnabled, setTool, startSketch, undo, worldToCanvas`
985
+
986
+ **VM ↔ Mac File Delivery:**
987
+ - Edit tool → VM overlay only (Mac never sees changes)
988
+ - `write_clipboard` → user pastes in Terminal → changes on Mac → git push works
989
+ - Always use Python heredoc scripts for complex multi-line fixes
990
+ - Pattern: `cd ~/cyclecad && python3 << 'PYEOF'\n...\nPYEOF\n&& git add ... && git commit ... && git push`
991
+
992
+ **GitHub Pages Cache:**
993
+ - Raw GitHub (`raw.githubusercontent.com`) updates instantly
994
+ - GitHub Pages CDN (`cyclecad.com`) can lag 5-10 minutes
995
+ - Cache-busting: `?bust=timestamp` in URL forces fresh fetch
996
+ - Meta tags help browser cache but NOT CDN cache
997
+ - Check source of truth: `https://raw.githubusercontent.com/vvlars-cmd/cyclecad/main/app/index.html`
998
+
999
+ ### What Was Built/Fixed This Session
1000
+
1001
+ | File | Action | What |
1002
+ |------|--------|------|
1003
+ | `cycleWASH-NRW-Prospects.xlsx` | NEW | 36 prospects, email templates DE+EN, follow-up tracker |
1004
+ | `app/js/modules/text-to-cad.js` | FIXED | Missing `(` after `if` on line 215 |
1005
+ | `app/index.html` | MAJOR | Splash screen, sketch wiring, ViewCube drag, Hard Reset, no-cache meta, dialog selector fixes, demo geometry removal |
1006
+ | `app/js/sketch.js` | EXISTING | Now imported and wired to menu actions |
1007
+
1008
+ ### index.html Changes Detail
1009
+
1010
+ **Sketch module import (line ~1447):**
1011
+ ```javascript
1012
+ import { startSketch, endSketch, setTool, getEntities, clearSketch, entitiesToGeometry } from './js/sketch.js';
1013
+ ```
1014
+
1015
+ **Sketch menu actions wired (in handleMenuAction switch):**
1016
+ - `sketch-new` → `startSketch('XY', camera, controls)`, shows sketch tools toolbar, activates Sketch tab
1017
+ - `sketch-line` → auto-enters sketch mode + `setTool('line')`
1018
+ - `sketch-circle` → auto-enters sketch mode + `setTool('circle')`
1019
+ - `sketch-rectangle` → auto-enters sketch mode + `setTool('rectangle')`
1020
+ - `sketch-arc` → auto-enters sketch mode + `setTool('arc')`
1021
+ - `sketch-polyline` → auto-enters sketch mode + `setTool('polyline')`
1022
+ - `sketch-finish` → `endSketch()`, converts entities to Three.js wireframe, adds to feature tree
1023
+
1024
+ **ViewCube (line ~1558):**
1025
+ - Pointer events for drag-to-rotate (uses THREE.Spherical for orbit)
1026
+ - Click-to-snap still works (detects drag vs click via `vcDragMoved` flag)
1027
+ - 120px size, grab/grabbing cursors, `touchAction: none` for Safari
1028
+
1029
+ **Hard Reset (Help menu):**
1030
+ - `data-action="help-hard-reset"` with `style="color:#ef4444"`
1031
+ - Handler clears: Service Workers, Cache API, localStorage, sessionStorage, IndexedDB
1032
+ - Reloads with `?reset=timestamp` cache buster
1033
+
1034
+ **Workspace tab wiring:**
1035
+ - Clicking "Sketch" tab auto-enters sketch mode
1036
+ - Clicking any other tab auto-finishes sketch and returns to that workspace
1037
+
1038
+ ### Bugs Still Present (Known Issues)
1039
+
1040
+ 1. **Version badge hardcoded** — Status bar shows `v0.9.0` but npm is at different version. Need to dynamically read from package.json or update on each publish.
1041
+ 2. **Sketch drawing untested end-to-end** — Import fix just pushed. Need to verify: click Circle → click center in viewport → drag radius → see circle drawn on 2D canvas overlay.
1042
+ 3. **ViewCube not visible sometimes** — The 120px ViewCube renders in top-right of `#viewport-container`. If the container has wrong dimensions or the renderer size doesn't match, it may not show.
1043
+ 4. **Three.js scene renders black/empty** — No demo geometry anymore. Until user draws something, viewport shows only grid lines (which may be hard to see on dark background).
1044
+ 5. **Safari private window caching** — Even with no-cache meta tags, Safari aggressively caches. Users must use `?bust=xxx` or Cmd+Shift+R.
1045
+
1046
+ ### Git State
1047
+ - All changes committed and pushed to `main` branch
1048
+ - Latest commits:
1049
+ - "Fix sketch.js import names (entitiesToShape->entitiesToGeometry)" ← **PENDING USER RUN** (clipboard copied)
1050
+ - "Wire sketch tools (line/circle/rect/arc) + no-cache meta tags"
1051
+ - "Fix ViewCube drag rotation + add Hard Reset button to Help menu"
1052
+ - Various splash screen fixes
1053
+
1054
+ ### npm State
1055
+ - **cyclecad**: Last published unknown version. User needs to run `cd ~/cyclecad && npm version patch && npm publish`
1056
+ - **explodeview**: v1.0.18 bumped locally
1057
+
1058
+ ### Collaboration Pattern Refinements (This Session)
1059
+
1060
+ | Pattern | Detail |
1061
+ |---------|--------|
1062
+ | **Clipboard delivery** | ALL code changes must go via `write_clipboard` → user pastes in Terminal. VM edits NEVER reach Mac. |
1063
+ | **Python heredoc scripts** | Best format for complex fixes: `python3 << 'PYEOF'\n...\nPYEOF` avoids quoting issues |
1064
+ | **Always verify on GitHub raw** | Before blaming "fix didn't work", check `raw.githubusercontent.com` to confirm push succeeded |
1065
+ | **Module import debugging** | Wrong import names silently kill entire module. Always verify with `import('./file.js').then(m => Object.keys(m))` |
1066
+ | **Cache frustration** | User gets frustrated when fixes are pushed but browser shows old version. Always provide cache-busting URL |
1067
+ | **Short messages = action needed** | "npm" = run npm publish. "done" = command was pasted and run. Don't ask, just provide the command. |
1068
+ | **User tests in Safari private** | Sachin tests in Safari private window. This has the most aggressive caching. Always consider Safari. |
1069
+
1070
+ ### Critical Architecture Notes
1071
+
1072
+ **app/index.html structure:**
1073
+ ```
1074
+ <head> — meta tags, CSS styles
1075
+ <div id="menu-bar"> — File/Edit/Sketch/Solid/Surface/Assembly/Drawing/Render/Animation/Inspect/Tools/Help menus
1076
+ <div id="container"> — Main layout
1077
+ <div id="viewport-container"> — Three.js canvas + ViewCube
1078
+ <div id="left-panel"> — Model tree / Assembly / Search tabs
1079
+ <div id="right-panel"> — Properties / Parameters / Material tabs
1080
+ <div id="welcome-panel"> — Splash screen (z-index: 9999)
1081
+ <script> IIFE — Splash button wiring (MUST be regular script, NOT module)
1082
+ <div id="dialog-overlay"> — Modal dialogs (z-index: 10000)
1083
+ <script type="module"> — ALL app logic:
1084
+ - Three.js imports + scene setup
1085
+ - sketch.js import
1086
+ - killer-features.js import
1087
+ - Camera, renderer, lights, grid
1088
+ - ViewCube (separate scene + renderer)
1089
+ - Animation loop
1090
+ - handleMenuAction() switch — ALL menu/toolbar actions
1091
+ - Workspace tab switching
1092
+ - Keyboard shortcuts
1093
+ - Event delegation for menu/toolbar clicks
1094
+ ```
1095
+
1096
+ **z-index hierarchy:**
1097
+ - `dialog-overlay`: 10000
1098
+ - `welcome-panel`: 9999
1099
+ - `toast-container`: 9999
1100
+ - ViewCube: 100
1101
+ - Sketch canvas: 30
1102
+
1103
+ ### Pending Tasks (Updated 2026-04-01)
1104
+ - [ ] **IMMEDIATE**: User needs to paste clipboard command fixing import names (entitiesToShape→entitiesToGeometry)
1105
+ - [ ] npm publish (user runs `cd ~/cyclecad && npm version patch && npm publish`)
1106
+ - [ ] Verify sketch circle drawing works end-to-end after import fix
1107
+ - [ ] Test ViewCube drag in Safari
1108
+ - [ ] Test Hard Reset button in Safari
1109
+ - [ ] Update version badge to read dynamically from package.json
1110
+ - [ ] Wire splash "New Sketch" button to actually trigger sketch-new action (currently only dismisses)
1111
+ - [ ] Wire splash "Open/Import" button to trigger file-import action
1112
+ - [ ] Wire splash "Text-to-CAD" button to trigger text-to-cad dialog
1113
+ - [ ] Wire splash "Inventor Project" button to trigger inventor import
1114
+ - [ ] ExplodeView route redirects push
1115
+ - [ ] Run test agents in Chrome and fix failures
1116
+ - [ ] Test STEP import with OpenCascade.js WASM
1117
+ - [ ] Build Text-to-CAD with Live Preview
1118
+ - [ ] Build Photo-to-CAD Reverse Engineering
1119
+
956
1120
  # currentDate
957
- Today's date is 2026-03-31.
1121
+ Today's date is 2026-04-01.
package/app/index.html CHANGED
@@ -1,6 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
5
+ <meta http-equiv="Pragma" content="no-cache">
6
+ <meta http-equiv="Expires" content="0">
4
7
  <meta charset="UTF-8">
5
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
9
  <title>cycleCAD - Fusion 360 Clone - Parametric 3D CAD Modeler</title>
@@ -989,6 +992,8 @@
989
992
  <button class="menu-item-link" data-action="help-docs">Documentation</button>
990
993
  <button class="menu-item-link" data-action="help-shortcuts">Keyboard Shortcuts (?)</button>
991
994
  <button class="menu-item-link" data-action="help-about">About cycleCAD</button>
995
+ <div style="border-top:1px solid #444;margin:4px 0;"></div>
996
+ <button class="menu-item-link" data-action="help-hard-reset" style="color:#ef4444;">Hard Reset (Clear Cache)</button>
992
997
  </div>
993
998
  </div>
994
999
  </div>
@@ -1148,7 +1153,7 @@
1148
1153
  <div id="viewport-container" style="position:relative;">
1149
1154
  <canvas id="viewport"></canvas>
1150
1155
  <!-- ViewCube -->
1151
- <div id="viewcube" style="position:absolute;top:12px;right:12px;width:90px;height:90px;pointer-events:auto;z-index:100;"></div>
1156
+ <div id="viewcube" style="position:absolute;top:12px;right:12px;width:120px;height:120px;pointer-events:auto;z-index:100;"></div>
1152
1157
  </div>
1153
1158
 
1154
1159
  <!-- Right Panel (Properties) -->
@@ -1308,38 +1313,6 @@
1308
1313
 
1309
1314
  </div>
1310
1315
 
1311
- <!-- Welcome Splash Screen -->
1312
- <div id="welcome-panel" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.75);display:flex;align-items:center;justify-content:center;z-index:9999;">
1313
- <div style="background:#252526;border:1px solid #3c3c3c;border-radius:12px;padding:40px 48px;max-width:560px;width:90%;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.5);">
1314
- <div style="font-size:36px;margin-bottom:4px;">
1315
- <span style="color:#0284C7;font-weight:700;">cycle</span><span style="color:#e0e0e0;font-weight:300;">CAD</span>
1316
- </div>
1317
- <div style="color:#888;font-size:13px;margin-bottom:28px;">Agent-First Parametric 3D CAD Modeler</div>
1318
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px;">
1319
- <button onclick="window._dismissSplash('sketch')" style="background:#0284C7;color:#fff;border:none;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;">
1320
- <div style="font-size:24px;margin-bottom:6px;">&#9999;&#65039;</div>
1321
- New Sketch
1322
- <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Start with a 2D sketch</div>
1323
- </button>
1324
- <button onclick="window._dismissSplash('import')" style="background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;">
1325
- <div style="font-size:24px;margin-bottom:6px;">&#128194;</div>
1326
- Open / Import
1327
- <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">STEP, STL, Inventor, JSON</div>
1328
- </button>
1329
- <button onclick="window._dismissSplash('textcad')" style="background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;">
1330
- <div style="font-size:24px;margin-bottom:6px;">&#129302;</div>
1331
- Text-to-CAD
1332
- <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Describe a part in English</div>
1333
- </button>
1334
- <button onclick="window._dismissSplash('inventor')" style="background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;">
1335
- <div style="font-size:24px;margin-bottom:6px;">&#127981;</div>
1336
- Inventor Project
1337
- <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Load .ipj / .ipt / .iam</div>
1338
- </button>
1339
- </div>
1340
- <div style="color:#666;font-size:11px;"><span id="splash-version">v0.9.0</span> &middot; 12 killer features &middot; 46 modules &middot; <a href="https://github.com/vvlars-cmd/cyclecad" target="_blank" style="color:#0284C7;text-decoration:none;">GitHub</a></div>
1341
- </div>
1342
- </div>
1343
1316
  <script>
1344
1317
  window._dismissSplash = function(action) {
1345
1318
  // Update splash version from status bar
@@ -1358,6 +1331,30 @@ window._dismissSplash = function(action) {
1358
1331
  };
1359
1332
  </script>
1360
1333
 
1334
+ <!-- Welcome Splash Screen -->
1335
+ <div id="welcome-panel" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.75);display:flex;align-items:center;justify-content:center;z-index:9999;">
1336
+ <div style="background:#252526;border:1px solid #3c3c3c;border-radius:12px;padding:40px 48px;max-width:560px;width:90%;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.5);">
1337
+ <div style="font-size:36px;margin-bottom:4px;"><span style="color:#0284C7;font-weight:700;">cycle</span><span style="color:#e0e0e0;font-weight:300;">CAD</span></div>
1338
+ <div style="color:#888;font-size:13px;margin-bottom:28px;">Agent-First Parametric 3D CAD Modeler</div>
1339
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px;">
1340
+ <button id="splash-sketch" style="background:#0284C7;color:#fff;border:none;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;"><div style="font-size:24px;margin-bottom:6px;">&#9999;&#65039;</div>New Sketch<div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Start with a 2D sketch</div></button>
1341
+ <button id="splash-import" style="background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;"><div style="font-size:24px;margin-bottom:6px;">&#128194;</div>Open / Import<div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">STEP, STL, Inventor, JSON</div></button>
1342
+ <button id="splash-textcad" style="background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;"><div style="font-size:24px;margin-bottom:6px;">&#129302;</div>Text-to-CAD<div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Describe a part in English</div></button>
1343
+ <button id="splash-inventor" style="background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;"><div style="font-size:24px;margin-bottom:6px;">&#127981;</div>Inventor Project<div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Load .ipj / .ipt / .iam</div></button>
1344
+ </div>
1345
+ <div style="color:#666;font-size:11px;">12 killer features &middot; 46 modules &middot; <a href="https://github.com/vvlars-cmd/cyclecad" target="_blank" style="color:#0284C7;text-decoration:none;">GitHub</a></div>
1346
+ </div>
1347
+ </div>
1348
+ <script>
1349
+ (function() {
1350
+ function dismiss() { document.getElementById("welcome-panel").style.display = "none"; }
1351
+ document.getElementById("splash-sketch").addEventListener("click", dismiss);
1352
+ document.getElementById("splash-import").addEventListener("click", dismiss);
1353
+ document.getElementById("splash-textcad").addEventListener("click", dismiss);
1354
+ document.getElementById("splash-inventor").addEventListener("click", dismiss);
1355
+ })();
1356
+ </script>
1357
+
1361
1358
  <!-- Modal Dialogs -->
1362
1359
  <div id="dialog-overlay" class="modal-overlay">
1363
1360
  <div class="modal-dialog">
@@ -1451,6 +1448,7 @@ window._dismissSplash = function(action) {
1451
1448
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
1452
1449
  import { GridHelper } from 'three';
1453
1450
  import { KillerFeatures } from './js/killer-features.js';
1451
+ import { startSketch, endSketch, setTool, getEntities, clearSketch, entitiesToGeometry } from './js/sketch.js';
1454
1452
 
1455
1453
  // ===== Three.js Viewport Setup =====
1456
1454
  const scene = new THREE.Scene();
@@ -1517,7 +1515,7 @@ window._dismissSplash = function(action) {
1517
1515
  const vcCamera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
1518
1516
  vcCamera.position.set(0, 0, 3.5);
1519
1517
  const vcRenderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
1520
- vcRenderer.setSize(90, 90);
1518
+ vcRenderer.setSize(120, 120);
1521
1519
  vcRenderer.setPixelRatio(window.devicePixelRatio);
1522
1520
  vcContainer.appendChild(vcRenderer.domElement);
1523
1521
 
@@ -1563,26 +1561,63 @@ window._dismissSplash = function(action) {
1563
1561
  };
1564
1562
  const vcRay = new THREE.Raycaster();
1565
1563
  const vcMouse = new THREE.Vector2();
1566
- vcRenderer.domElement.addEventListener('click', (e) => {
1567
- const rect = vcRenderer.domElement.getBoundingClientRect();
1568
- vcMouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
1569
- vcMouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
1570
- vcRay.setFromCamera(vcMouse, vcCamera);
1571
- const hits = vcRay.intersectObject(vcCube);
1572
- if (hits.length > 0) {
1573
- const fi = hits[0].face.materialIndex;
1574
- const label = faceLabels[fi];
1575
- const v = vcViews[label];
1576
- if (v) {
1577
- const dist = camera.position.length();
1578
- const s = dist / 3;
1579
- camera.position.set(v.x * s, v.y * s, v.z * s);
1580
- camera.lookAt(controls.target);
1581
- controls.update();
1564
+ // ViewCube drag-to-rotate + click-to-snap
1565
+ let vcDragging = false, vcDragStart = null, vcDragMoved = false;
1566
+ const vcEl = vcRenderer.domElement;
1567
+ vcEl.addEventListener('pointerdown', (e) => {
1568
+ vcDragging = true; vcDragMoved = false;
1569
+ vcDragStart = { x: e.clientX, y: e.clientY };
1570
+ vcEl.setPointerCapture(e.pointerId);
1571
+ e.preventDefault(); e.stopPropagation();
1572
+ });
1573
+ vcEl.addEventListener('pointermove', (e) => {
1574
+ if (!vcDragging || !vcDragStart) return;
1575
+ const dx = e.clientX - vcDragStart.x;
1576
+ const dy = e.clientY - vcDragStart.y;
1577
+ if (Math.abs(dx) > 2 || Math.abs(dy) > 2) vcDragMoved = true;
1578
+ if (!vcDragMoved) return;
1579
+ const speed = 0.01;
1580
+ const spherical = new THREE.Spherical().setFromVector3(
1581
+ camera.position.clone().sub(controls.target)
1582
+ );
1583
+ spherical.theta -= dx * speed;
1584
+ spherical.phi -= dy * speed;
1585
+ spherical.phi = Math.max(0.05, Math.min(Math.PI - 0.05, spherical.phi));
1586
+ camera.position.copy(
1587
+ new THREE.Vector3().setFromSpherical(spherical).add(controls.target)
1588
+ );
1589
+ camera.lookAt(controls.target);
1590
+ controls.update();
1591
+ vcDragStart = { x: e.clientX, y: e.clientY };
1592
+ e.preventDefault(); e.stopPropagation();
1593
+ });
1594
+ vcEl.addEventListener('pointerup', (e) => {
1595
+ if (vcDragging && !vcDragMoved) {
1596
+ const rect = vcEl.getBoundingClientRect();
1597
+ vcMouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
1598
+ vcMouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
1599
+ vcRay.setFromCamera(vcMouse, vcCamera);
1600
+ const hits = vcRay.intersectObject(vcCube);
1601
+ if (hits.length > 0) {
1602
+ const fi = hits[0].face.materialIndex;
1603
+ const label = faceLabels[fi];
1604
+ const v = vcViews[label];
1605
+ if (v) {
1606
+ const dist = camera.position.length();
1607
+ const s = dist / 3;
1608
+ camera.position.set(v.x * s, v.y * s, v.z * s);
1609
+ camera.lookAt(controls.target);
1610
+ controls.update();
1611
+ }
1582
1612
  }
1583
1613
  }
1614
+ vcDragging = false; vcDragStart = null; vcDragMoved = false;
1615
+ e.preventDefault(); e.stopPropagation();
1584
1616
  });
1585
- vcRenderer.domElement.style.cursor = 'pointer';
1617
+ vcEl.style.cursor = 'grab';
1618
+ vcEl.addEventListener('pointerdown', () => { vcEl.style.cursor = 'grabbing'; });
1619
+ vcEl.addEventListener('pointerup', () => { vcEl.style.cursor = 'grab'; });
1620
+ vcEl.style.touchAction = 'none';
1586
1621
 
1587
1622
  // Animation loop
1588
1623
  function animate() {
@@ -1619,6 +1654,23 @@ window._dismissSplash = function(action) {
1619
1654
  // ===== DOM Queries =====
1620
1655
  const menuBar = document.getElementById('menu-bar');
1621
1656
  const workspaceTabs = document.querySelectorAll('.workspace-tab');
1657
+ // Wire workspace tabs to enter/exit sketch mode
1658
+ workspaceTabs.forEach(tab => {
1659
+ tab.addEventListener('click', () => {
1660
+ const ws = tab.getAttribute('data-workspace');
1661
+ workspaceTabs.forEach(t => t.classList.remove('active'));
1662
+ tab.classList.add('active');
1663
+ // Show/hide workspace-specific toolbars
1664
+ document.querySelectorAll('.workspace-tools').forEach(t => t.style.display = 'none');
1665
+ const wsTools = document.getElementById(ws + '-tools');
1666
+ if (wsTools) wsTools.style.display = 'flex';
1667
+ if (ws === 'sketch' && !appState.isSketchMode) {
1668
+ handleMenuAction('sketch-new');
1669
+ } else if (ws !== 'sketch' && appState.isSketchMode) {
1670
+ handleMenuAction('sketch-finish');
1671
+ }
1672
+ });
1673
+ });
1622
1674
  const leftPanelTabs = document.querySelectorAll('.left-panel-tab');
1623
1675
  const rightPanelTabs = document.querySelectorAll('.right-panel-tab');
1624
1676
  const viewportContainer = document.getElementById('viewport-container');
@@ -1653,8 +1705,98 @@ window._dismissSplash = function(action) {
1653
1705
  case 'edit-redo':
1654
1706
  showToast('Redo', 'success');
1655
1707
  break;
1708
+ case 'sketch-new':
1709
+ if (!appState.isSketchMode) {
1710
+ appState.isSketchMode = true;
1711
+ startSketch('XY', camera, controls);
1712
+ showToast('Sketch mode — draw on XY plane', 'success');
1713
+ // Show sketch tools, activate Sketch tab
1714
+ var st = document.getElementById('sketch-tools');
1715
+ if (st) st.style.display = 'flex';
1716
+ document.querySelectorAll('.workspace-tab').forEach(t => t.classList.remove('active'));
1717
+ var skTab = document.querySelector('[data-workspace="sketch"]');
1718
+ if (skTab) skTab.classList.add('active');
1719
+ }
1720
+ break;
1656
1721
  case 'sketch-line':
1657
- if (appState.isSketchMode) showToast('Line tool active', 'success');
1722
+ if (!appState.isSketchMode) { handleMenuAction('sketch-new'); }
1723
+ setTool('line');
1724
+ showToast('Line tool — click to place points', 'success');
1725
+ break;
1726
+ case 'sketch-rectangle':
1727
+ if (!appState.isSketchMode) { handleMenuAction('sketch-new'); }
1728
+ setTool('rectangle');
1729
+ showToast('Rectangle tool — click two corners', 'success');
1730
+ break;
1731
+ case 'sketch-circle':
1732
+ if (!appState.isSketchMode) { handleMenuAction('sketch-new'); }
1733
+ setTool('circle');
1734
+ showToast('Circle tool — click center, then radius', 'success');
1735
+ break;
1736
+ case 'sketch-arc':
1737
+ if (!appState.isSketchMode) { handleMenuAction('sketch-new'); }
1738
+ setTool('arc');
1739
+ showToast('Arc tool — click 3 points', 'success');
1740
+ break;
1741
+ case 'sketch-polyline':
1742
+ if (!appState.isSketchMode) { handleMenuAction('sketch-new'); }
1743
+ setTool('polyline');
1744
+ showToast('Polyline tool — click points, double-click to finish', 'success');
1745
+ break;
1746
+ case 'sketch-finish':
1747
+ if (appState.isSketchMode) {
1748
+ const entities = endSketch();
1749
+ appState.isSketchMode = false;
1750
+ // Hide sketch tools, back to Design tab
1751
+ var st2 = document.getElementById('sketch-tools');
1752
+ if (st2) st2.style.display = 'none';
1753
+ document.querySelectorAll('.workspace-tab').forEach(t => t.classList.remove('active'));
1754
+ var dTab = document.querySelector('[data-workspace="design"]');
1755
+ if (dTab) dTab.classList.add('active');
1756
+ if (entities.length > 0) {
1757
+ // Convert sketch entities to 3D wireframe in scene
1758
+ try {
1759
+ const shape = entitiesToGeometry(entities);
1760
+ if (shape) {
1761
+ const mat = new THREE.LineBasicMaterial({ color: 0x00ff00 });
1762
+ const edges = new THREE.EdgesGeometry(shape);
1763
+ const wireframe = new THREE.LineSegments(edges, mat);
1764
+ wireframe.name = 'Sketch' + (appState.featureTree.length + 1);
1765
+ scene.add(wireframe);
1766
+ appState.featureTree.push({ name: wireframe.name, type: 'Sketch', visible: true, mesh: wireframe });
1767
+ showToast('Sketch finished — ' + entities.length + ' entities', 'success');
1768
+ } else {
1769
+ // Just add lines directly
1770
+ const lineMat = new THREE.LineBasicMaterial({ color: 0x00ccff });
1771
+ entities.forEach((ent, idx) => {
1772
+ if (ent.points && ent.points.length >= 2) {
1773
+ const pts = ent.points.map(p => new THREE.Vector3(p.x, p.y, 0));
1774
+ if (ent.type === 'circle' && ent.center && ent.radius) {
1775
+ const curve = new THREE.EllipseCurve(ent.center.x, ent.center.y, ent.radius, ent.radius, 0, Math.PI * 2, false, 0);
1776
+ const cPts = curve.getPoints(64).map(p => new THREE.Vector3(p.x, p.y, 0));
1777
+ const geo = new THREE.BufferGeometry().setFromPoints(cPts);
1778
+ const line = new THREE.Line(geo, lineMat);
1779
+ line.name = 'SketchCircle' + (idx + 1);
1780
+ scene.add(line);
1781
+ } else {
1782
+ const geo = new THREE.BufferGeometry().setFromPoints(pts);
1783
+ const line = new THREE.Line(geo, lineMat);
1784
+ line.name = 'SketchLine' + (idx + 1);
1785
+ scene.add(line);
1786
+ }
1787
+ }
1788
+ });
1789
+ appState.featureTree.push({ name: 'Sketch' + (appState.featureTree.length + 1), type: 'Sketch', visible: true });
1790
+ showToast('Sketch finished — ' + entities.length + ' entities added', 'success');
1791
+ }
1792
+ } catch(e) {
1793
+ console.warn('Sketch to shape error:', e);
1794
+ showToast('Sketch finished (wireframe only)', 'warning');
1795
+ }
1796
+ } else {
1797
+ showToast('Empty sketch discarded', 'warning');
1798
+ }
1799
+ }
1658
1800
  break;
1659
1801
  case 'solid-extrude':
1660
1802
  showToast('Extrude tool active', 'success');
@@ -1671,6 +1813,29 @@ window._dismissSplash = function(action) {
1671
1813
  case 'help-docs':
1672
1814
  window.open('https://cyclecad.com/docs', '_blank');
1673
1815
  break;
1816
+ case 'help-hard-reset':
1817
+ if (confirm('This will clear ALL cached data and reload. Continue?')) {
1818
+ (async () => {
1819
+ try {
1820
+ if ('serviceWorker' in navigator) {
1821
+ const regs = await navigator.serviceWorker.getRegistrations();
1822
+ for (const r of regs) await r.unregister();
1823
+ }
1824
+ if ('caches' in window) {
1825
+ const keys = await caches.keys();
1826
+ for (const k of keys) await caches.delete(k);
1827
+ }
1828
+ try { localStorage.clear(); } catch(e) {}
1829
+ try { sessionStorage.clear(); } catch(e) {}
1830
+ if (indexedDB.databases) {
1831
+ const dbs = await indexedDB.databases();
1832
+ for (const db of dbs) indexedDB.deleteDatabase(db.name);
1833
+ }
1834
+ window.location.href = window.location.pathname + '?reset=' + Date.now();
1835
+ } catch(e) { window.location.reload(true); }
1836
+ })();
1837
+ }
1838
+ break;
1674
1839
  case 'help-about':
1675
1840
  showDialog('About cycleCAD', 'cycleCAD v0.9.0 - Fusion 360 Clone<br>Open-source parametric 3D CAD modeler<br><br>Built with Three.js, supporting STEP/IGES import, full parametric modeling, and AI-powered design assistance.');
1676
1841
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyclecad",
3
- "version": "3.9.4",
3
+ "version": "3.9.6",
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": {