cyclecad 3.9.1 → 3.9.3

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/_test_write ADDED
File without changes
package/app/index.html CHANGED
@@ -1145,8 +1145,10 @@
1145
1145
  </div>
1146
1146
 
1147
1147
  <!-- Viewport -->
1148
- <div id="viewport-container">
1148
+ <div id="viewport-container" style="position:relative;">
1149
1149
  <canvas id="viewport"></canvas>
1150
+ <!-- ViewCube -->
1151
+ <div id="viewcube" style="position:absolute;top:12px;right:12px;width:90px;height:90px;pointer-events:auto;z-index:100;"></div>
1150
1152
  </div>
1151
1153
 
1152
1154
  <!-- Right Panel (Properties) -->
@@ -1306,6 +1308,52 @@
1306
1308
 
1307
1309
  </div>
1308
1310
 
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;">v0.9.0 &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
+ <script>
1344
+ window._dismissSplash = function(action) {
1345
+ document.getElementById('welcome-panel').style.display = 'none';
1346
+ if (action === 'sketch') {
1347
+ // Will be handled by module script once loaded
1348
+ window._pendingSplashAction = 'sketch-new';
1349
+ } else if (action === 'import' || action === 'inventor') {
1350
+ window._pendingSplashAction = 'file-import';
1351
+ } else if (action === 'textcad') {
1352
+ window._pendingSplashAction = 'tools-text-to-cad';
1353
+ }
1354
+ };
1355
+ </script>
1356
+
1309
1357
  <!-- Modal Dialogs -->
1310
1358
  <div id="dialog-overlay" class="modal-overlay">
1311
1359
  <div class="modal-dialog">
@@ -1447,22 +1495,6 @@
1447
1495
  const grid = new THREE.GridHelper(500, 50, 0x444444, 0x333333);
1448
1496
  scene.add(grid);
1449
1497
 
1450
- // Demo geometry — a sample part so viewport isn't empty
1451
- const boxGeo = new THREE.BoxGeometry(60, 40, 80);
1452
- const boxMat = new THREE.MeshStandardMaterial({ color: 0x0284C7, metalness: 0.3, roughness: 0.6 });
1453
- const box = new THREE.Mesh(boxGeo, boxMat);
1454
- box.position.y = 20;
1455
- box.castShadow = true;
1456
- box.receiveShadow = true;
1457
- scene.add(box);
1458
-
1459
- const cylGeo = new THREE.CylinderGeometry(12, 12, 50, 32);
1460
- const cylMat = new THREE.MeshStandardMaterial({ color: 0x10b981, metalness: 0.4, roughness: 0.5 });
1461
- const cyl = new THREE.Mesh(cylGeo, cylMat);
1462
- cyl.position.set(0, 45, 0);
1463
- cyl.castShadow = true;
1464
- scene.add(cyl);
1465
-
1466
1498
  // Ground plane
1467
1499
  const groundGeo = new THREE.PlaneGeometry(500, 500);
1468
1500
  const groundMat = new THREE.ShadowMaterial({ opacity: 0.15 });
@@ -1475,11 +1507,89 @@
1475
1507
  new ResizeObserver(resizeViewport).observe(viewportEl);
1476
1508
  resizeViewport(); // Call initial resize to set canvas dimensions
1477
1509
 
1510
+ // ===== ViewCube =====
1511
+ const vcContainer = document.getElementById('viewcube');
1512
+ const vcScene = new THREE.Scene();
1513
+ const vcCamera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
1514
+ vcCamera.position.set(0, 0, 3.5);
1515
+ const vcRenderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
1516
+ vcRenderer.setSize(90, 90);
1517
+ vcRenderer.setPixelRatio(window.devicePixelRatio);
1518
+ vcContainer.appendChild(vcRenderer.domElement);
1519
+
1520
+ // Cube faces
1521
+ const faceLabels = ['Right', 'Left', 'Top', 'Bottom', 'Front', 'Back'];
1522
+ const faceColors = [0x3b82f6, 0x3b82f6, 0x10b981, 0x10b981, 0x0284C7, 0x0284C7];
1523
+ const cubeMats = faceLabels.map((label, i) => {
1524
+ const canvas = document.createElement('canvas');
1525
+ canvas.width = 128; canvas.height = 128;
1526
+ const ctx = canvas.getContext('2d');
1527
+ ctx.fillStyle = '#' + faceColors[i].toString(16).padStart(6, '0');
1528
+ ctx.fillRect(0, 0, 128, 128);
1529
+ ctx.strokeStyle = '#1e293b';
1530
+ ctx.lineWidth = 3;
1531
+ ctx.strokeRect(1, 1, 126, 126);
1532
+ ctx.fillStyle = '#fff';
1533
+ ctx.font = 'bold 22px Arial';
1534
+ ctx.textAlign = 'center';
1535
+ ctx.textBaseline = 'middle';
1536
+ ctx.fillText(label, 64, 64);
1537
+ const tex = new THREE.CanvasTexture(canvas);
1538
+ return new THREE.MeshBasicMaterial({ map: tex });
1539
+ });
1540
+ const vcCube = new THREE.Mesh(new THREE.BoxGeometry(1.6, 1.6, 1.6), cubeMats);
1541
+ vcScene.add(vcCube);
1542
+
1543
+ // Axis lines on viewcube
1544
+ const axLen = 1.3;
1545
+ const axMat = (c) => new THREE.LineBasicMaterial({ color: c });
1546
+ const axGeo = (v) => new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), v]);
1547
+ vcScene.add(new THREE.Line(axGeo(new THREE.Vector3(axLen, 0, 0)), axMat(0xff4444)));
1548
+ vcScene.add(new THREE.Line(axGeo(new THREE.Vector3(0, axLen, 0)), axMat(0x44ff44)));
1549
+ vcScene.add(new THREE.Line(axGeo(new THREE.Vector3(0, 0, axLen)), axMat(0x4444ff)));
1550
+
1551
+ // ViewCube click → snap camera to face
1552
+ const vcViews = {
1553
+ Right: { x: 3, y: 0, z: 0 },
1554
+ Left: { x: -3, y: 0, z: 0 },
1555
+ Top: { x: 0, y: 3, z: 0 },
1556
+ Bottom: { x: 0, y: -3, z: 0 },
1557
+ Front: { x: 0, y: 0, z: 3 },
1558
+ Back: { x: 0, y: 0, z: -3 }
1559
+ };
1560
+ const vcRay = new THREE.Raycaster();
1561
+ const vcMouse = new THREE.Vector2();
1562
+ vcRenderer.domElement.addEventListener('click', (e) => {
1563
+ const rect = vcRenderer.domElement.getBoundingClientRect();
1564
+ vcMouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
1565
+ vcMouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
1566
+ vcRay.setFromCamera(vcMouse, vcCamera);
1567
+ const hits = vcRay.intersectObject(vcCube);
1568
+ if (hits.length > 0) {
1569
+ const fi = hits[0].face.materialIndex;
1570
+ const label = faceLabels[fi];
1571
+ const v = vcViews[label];
1572
+ if (v) {
1573
+ const dist = camera.position.length();
1574
+ const s = dist / 3;
1575
+ camera.position.set(v.x * s, v.y * s, v.z * s);
1576
+ camera.lookAt(controls.target);
1577
+ controls.update();
1578
+ }
1579
+ }
1580
+ });
1581
+ vcRenderer.domElement.style.cursor = 'pointer';
1582
+
1478
1583
  // Animation loop
1479
1584
  function animate() {
1480
1585
  requestAnimationFrame(animate);
1481
1586
  controls.update();
1482
1587
  renderer.render(scene, camera);
1588
+ // Sync ViewCube with main camera
1589
+ const dir = new THREE.Vector3();
1590
+ camera.getWorldDirection(dir);
1591
+ vcCube.quaternion.copy(camera.quaternion).invert();
1592
+ vcRenderer.render(vcScene, vcCamera);
1483
1593
  }
1484
1594
  animate();
1485
1595
 
@@ -1562,105 +1672,106 @@
1562
1672
  break;
1563
1673
  case 'tools-text-to-cad':
1564
1674
  if (window.CycleCAD && window.CycleCAD.TextToCAD) {
1565
- const ttcPanel = window.CycleCAD.TextToCAD.getUI();
1566
1675
  showDialog('Text-to-CAD (AI)', '');
1567
- const dlg = document.querySelector('.dialog-content');
1568
- if (dlg) { dlg.innerHTML = ''; dlg.appendChild(ttcPanel); dlg.style.maxHeight = '500px'; dlg.style.overflow = 'auto'; }
1569
- }
1676
+ const db1 = document.getElementById('dialog-body');
1677
+ if (db1) { db1.innerHTML = ''; db1.appendChild(window.CycleCAD.TextToCAD.getUI()); db1.style.maxHeight = '500px'; db1.style.overflow = 'auto'; }
1678
+ } else { showToast('TextToCAD module not loaded', 'error'); }
1570
1679
  break;
1571
1680
  case 'tools-photo-to-cad':
1572
1681
  if (window.CycleCAD && window.CycleCAD.PhotoToCAD) {
1573
- const ptcPanel = window.CycleCAD.PhotoToCAD.getUI();
1574
1682
  showDialog('Photo-to-CAD', '');
1575
- const dlg2 = document.querySelector('.dialog-content');
1576
- if (dlg2) { dlg2.innerHTML = ''; dlg2.appendChild(ptcPanel); dlg2.style.maxHeight = '500px'; dlg2.style.overflow = 'auto'; }
1577
- }
1683
+ const db2 = document.getElementById('dialog-body');
1684
+ if (db2) { db2.innerHTML = ''; db2.appendChild(window.CycleCAD.PhotoToCAD.getUI()); db2.style.maxHeight = '500px'; db2.style.overflow = 'auto'; }
1685
+ } else { showToast('PhotoToCAD module not loaded', 'error'); }
1578
1686
  break;
1579
1687
  case 'tools-dfm':
1580
1688
  if (window.CycleCAD && window.CycleCAD.Manufacturability) {
1581
- const dfmPanel = window.CycleCAD.Manufacturability.getUI();
1582
1689
  showDialog('Manufacturability Check', '');
1583
- const dlg3 = document.querySelector('.dialog-content');
1584
- if (dlg3) { dlg3.innerHTML = ''; dlg3.appendChild(dfmPanel); dlg3.style.maxHeight = '500px'; dlg3.style.overflow = 'auto'; }
1585
- }
1690
+ const db3 = document.getElementById('dialog-body');
1691
+ if (db3) { db3.innerHTML = ''; db3.appendChild(window.CycleCAD.Manufacturability.getUI()); db3.style.maxHeight = '500px'; db3.style.overflow = 'auto'; }
1692
+ } else { showToast('Manufacturability module not loaded', 'error'); }
1586
1693
  break;
1587
1694
  case 'tools-generative':
1588
1695
  if (window.CycleCAD && window.CycleCAD.GenerativeDesign) {
1589
- const genPanel = window.CycleCAD.GenerativeDesign.getUI();
1590
1696
  showDialog('Generative Design', '');
1591
- const dlg4 = document.querySelector('.dialog-content');
1592
- if (dlg4) { dlg4.innerHTML = ''; dlg4.appendChild(genPanel); dlg4.style.maxHeight = '500px'; dlg4.style.overflow = 'auto'; }
1593
- }
1697
+ const db4 = document.getElementById('dialog-body');
1698
+ if (db4) { db4.innerHTML = ''; db4.appendChild(window.CycleCAD.GenerativeDesign.getUI()); db4.style.maxHeight = '500px'; db4.style.overflow = 'auto'; }
1699
+ } else { showToast('GenerativeDesign module not loaded', 'error'); }
1594
1700
  break;
1595
1701
  case 'tools-physics':
1596
1702
  if (window.CycleCAD && window.CycleCAD.MultiPhysics) {
1597
- const physPanel = window.CycleCAD.MultiPhysics.getUI();
1598
1703
  showDialog('Multi-Physics Simulation', '');
1599
- const dlg5 = document.querySelector('.dialog-content');
1600
- if (dlg5) { dlg5.innerHTML = ''; dlg5.appendChild(physPanel); dlg5.style.maxHeight = '500px'; dlg5.style.overflow = 'auto'; }
1601
- }
1704
+ const db5 = document.getElementById('dialog-body');
1705
+ if (db5) { db5.innerHTML = ''; db5.appendChild(window.CycleCAD.MultiPhysics.getUI()); db5.style.maxHeight = '500px'; db5.style.overflow = 'auto'; }
1706
+ } else { showToast('MultiPhysics module not loaded', 'error'); }
1602
1707
  break;
1603
1708
  case 'tools-parts':
1604
1709
  if (window.CycleCAD && window.CycleCAD.SmartParts) {
1605
- const partsPanel = window.CycleCAD.SmartParts.getUI();
1606
1710
  showDialog('Smart Parts Library', '');
1607
- const dlg6 = document.querySelector('.dialog-content');
1608
- if (dlg6) { dlg6.innerHTML = ''; dlg6.appendChild(partsPanel); dlg6.style.maxHeight = '500px'; dlg6.style.overflow = 'auto'; }
1609
- }
1711
+ const db6 = document.getElementById('dialog-body');
1712
+ if (db6) { db6.innerHTML = ''; db6.appendChild(window.CycleCAD.SmartParts.getUI()); db6.style.maxHeight = '500px'; db6.style.overflow = 'auto'; }
1713
+ } else { showToast('SmartParts module not loaded', 'error'); }
1610
1714
  break;
1611
1715
  case 'tools-smart-assembly':
1612
1716
  if (window.CycleCAD && window.CycleCAD.SmartAssembly) {
1613
- const saPanel = window.CycleCAD.SmartAssembly.getUI();
1614
1717
  showDialog('Smart Assembly Mating', '');
1615
- const dlg7 = document.querySelector('.dialog-content');
1616
- if (dlg7) { dlg7.innerHTML = ''; dlg7.appendChild(saPanel); dlg7.style.maxHeight = '500px'; dlg7.style.overflow = 'auto'; }
1617
- }
1718
+ const db7 = document.getElementById('dialog-body');
1719
+ if (db7) { db7.innerHTML = ''; db7.appendChild(window.CycleCAD.SmartAssembly.getUI()); db7.style.maxHeight = '500px'; db7.style.overflow = 'auto'; }
1720
+ } else { showToast('SmartAssembly module not loaded', 'error'); }
1618
1721
  break;
1619
1722
  case 'tools-digital-twin':
1620
1723
  if (window.CycleCAD && window.CycleCAD.DigitalTwin) {
1621
- const dtPanel = window.CycleCAD.DigitalTwin.getUI();
1622
1724
  showDialog('Digital Twin — Live Data', '');
1623
- const dlg8 = document.querySelector('.dialog-content');
1624
- if (dlg8) { dlg8.innerHTML = ''; dlg8.appendChild(dtPanel); dlg8.style.maxHeight = '500px'; dlg8.style.overflow = 'auto'; }
1625
- }
1725
+ const db8 = document.getElementById('dialog-body');
1726
+ if (db8) { db8.innerHTML = ''; db8.appendChild(window.CycleCAD.DigitalTwin.getUI()); db8.style.maxHeight = '500px'; db8.style.overflow = 'auto'; }
1727
+ } else { showToast('DigitalTwin module not loaded', 'error'); }
1626
1728
  break;
1627
1729
  case 'tools-machine':
1628
1730
  if (window.CycleCAD && window.CycleCAD.MachineControl) {
1629
- const mcPanel = window.CycleCAD.MachineControl.getUI();
1630
1731
  showDialog('Machine Control — CNC / 3D Printer', '');
1631
- const dlg9 = document.querySelector('.dialog-content');
1632
- if (dlg9) { dlg9.innerHTML = ''; dlg9.appendChild(mcPanel); dlg9.style.maxHeight = '500px'; dlg9.style.overflow = 'auto'; }
1633
- }
1732
+ const db9 = document.getElementById('dialog-body');
1733
+ if (db9) { db9.innerHTML = ''; db9.appendChild(window.CycleCAD.MachineControl.getUI()); db9.style.maxHeight = '500px'; db9.style.overflow = 'auto'; }
1734
+ } else { showToast('MachineControl module not loaded', 'error'); }
1634
1735
  break;
1635
1736
  case 'tools-notebook':
1636
1737
  if (window.CycleCAD && window.CycleCAD.EngineeringNotebook) {
1637
- const nbPanel = window.CycleCAD.EngineeringNotebook.getUI();
1638
1738
  showDialog('Engineering Notebook', '');
1639
- const dlg10 = document.querySelector('.dialog-content');
1640
- if (dlg10) { dlg10.innerHTML = ''; dlg10.appendChild(nbPanel); dlg10.style.maxHeight = '500px'; dlg10.style.overflow = 'auto'; }
1641
- }
1739
+ const db10 = document.getElementById('dialog-body');
1740
+ if (db10) { db10.innerHTML = ''; db10.appendChild(window.CycleCAD.EngineeringNotebook.getUI()); db10.style.maxHeight = '500px'; db10.style.overflow = 'auto'; }
1741
+ } else { showToast('EngineeringNotebook module not loaded', 'error'); }
1642
1742
  break;
1643
1743
  case 'tools-auto-assembly':
1644
1744
  if (window.CycleCAD && window.CycleCAD.AutoAssembly) {
1645
- const aaPanel = window.CycleCAD.AutoAssembly.getUI();
1646
1745
  showDialog('Auto-Assemble Parts', '');
1647
- const dlg11 = document.querySelector('.dialog-content');
1648
- if (dlg11) { dlg11.innerHTML = ''; dlg11.appendChild(aaPanel); dlg11.style.maxHeight = '500px'; dlg11.style.overflow = 'auto'; }
1649
- }
1746
+ const db11 = document.getElementById('dialog-body');
1747
+ if (db11) { db11.innerHTML = ''; db11.appendChild(window.CycleCAD.AutoAssembly.getUI()); db11.style.maxHeight = '500px'; db11.style.overflow = 'auto'; }
1748
+ } else { showToast('AutoAssembly module not loaded', 'error'); }
1650
1749
  break;
1651
1750
  case 'tools-parametric':
1652
1751
  if (window.CycleCAD && window.CycleCAD.ParametricFromExample) {
1653
- const pfPanel = window.CycleCAD.ParametricFromExample.getUI();
1654
1752
  showDialog('Parametric from Example', '');
1655
- const dlg12 = document.querySelector('.dialog-content');
1656
- if (dlg12) { dlg12.innerHTML = ''; dlg12.appendChild(pfPanel); dlg12.style.maxHeight = '500px'; dlg12.style.overflow = 'auto'; }
1657
- }
1753
+ const db12 = document.getElementById('dialog-body');
1754
+ if (db12) { db12.innerHTML = ''; db12.appendChild(window.CycleCAD.ParametricFromExample.getUI()); db12.style.maxHeight = '500px'; db12.style.overflow = 'auto'; }
1755
+ } else { showToast('ParametricFromExample module not loaded', 'error'); }
1658
1756
  break;
1659
1757
  default:
1660
1758
  showToast(`${action}`, 'success');
1661
1759
  }
1662
1760
  }
1663
1761
 
1762
+ // ===== Handle pending splash action =====
1763
+ if (window._pendingSplashAction) {
1764
+ const pa = window._pendingSplashAction;
1765
+ window._pendingSplashAction = null;
1766
+ handleMenuAction(pa);
1767
+ }
1768
+ // Process splash actions dispatched after module load
1769
+ Object.defineProperty(window, '_pendingSplashAction', {
1770
+ set(v) { if (v) { setTimeout(() => handleMenuAction(v), 100); } },
1771
+ get() { return null; },
1772
+ configurable: true
1773
+ });
1774
+
1664
1775
  // ===== Workspace Switching =====
1665
1776
  workspaceTabs.forEach(tab => {
1666
1777
  tab.addEventListener('click', (e) => {
@@ -212,7 +212,7 @@
212
212
  if (/add|with|plus/.test(lower)) return 'add';
213
213
  if (/(fillet|chamfer|pattern|shell|subtract|cut)/.test(lower)) return 'modify';
214
214
  if (/combine|merge|join|union/.test(lower)) return 'combine';
215
- if /(array|repeat|pattern)/.test(lower)) return 'pattern';
215
+ if (/(array|repeat|pattern)/.test(lower)) return 'pattern';
216
216
  if (/export|save|output/.test(lower)) return 'export';
217
217
  return 'create';
218
218
  }
Binary file
@@ -0,0 +1,123 @@
1
+ #!/bin/bash
2
+ # Fix splash screen buttons - v2 (uses global onclick + inline handlers)
3
+ # Run from ~/cyclecad: bash fix-splash-v2.sh
4
+
5
+ set -e
6
+ cd "$(dirname "$0")"
7
+
8
+ echo "=== Replacing welcome splash with working version ==="
9
+
10
+ python3 << 'PYEOF'
11
+ with open('app/index.html', 'r') as f:
12
+ content = f.read()
13
+
14
+ # Remove old welcome panel if it exists
15
+ import re
16
+ content = re.sub(
17
+ r'<!-- Welcome Splash Screen -->.*?</div>\s*</div>\s*</div>\s*\n\n',
18
+ '',
19
+ content,
20
+ flags=re.DOTALL
21
+ )
22
+
23
+ # Remove old welcome splash JS wiring if it exists
24
+ content = content.replace(
25
+ """ // ===== Welcome Splash =====
26
+ const welcomePanel = document.getElementById('welcome-panel');
27
+ if (welcomePanel) {
28
+ const dismissWelcome = () => { welcomePanel.style.display = 'none'; };
29
+ const wb1 = welcomePanel.querySelector('[data-action="new-sketch"]');
30
+ if (wb1) wb1.onclick = () => { dismissWelcome(); handleMenuAction('sketch-new'); };
31
+ const wb2 = welcomePanel.querySelector('[data-action="import"]');
32
+ if (wb2) wb2.onclick = () => { dismissWelcome(); handleMenuAction('file-import'); };
33
+ const wb3 = welcomePanel.querySelector('[data-action="ai-generate"]');
34
+ if (wb3) wb3.onclick = () => { dismissWelcome(); handleMenuAction('tools-text-to-cad'); };
35
+ const wb4 = welcomePanel.querySelector('[data-action="load-inventor"]');
36
+ if (wb4) wb4.onclick = () => { dismissWelcome(); handleMenuAction('file-import'); };
37
+ }
38
+
39
+ """,
40
+ ""
41
+ )
42
+
43
+ # New splash with INLINE onclick handlers that use a global function
44
+ new_splash = '''<!-- Welcome Splash Screen -->
45
+ <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;">
46
+ <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);">
47
+ <div style="font-size:36px;margin-bottom:4px;">
48
+ <span style="color:#0284C7;font-weight:700;">cycle</span><span style="color:#e0e0e0;font-weight:300;">CAD</span>
49
+ </div>
50
+ <div style="color:#888;font-size:13px;margin-bottom:28px;">Agent-First Parametric 3D CAD Modeler</div>
51
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px;">
52
+ <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;">
53
+ <div style="font-size:24px;margin-bottom:6px;">&#9999;&#65039;</div>
54
+ New Sketch
55
+ <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Start with a 2D sketch</div>
56
+ </button>
57
+ <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;">
58
+ <div style="font-size:24px;margin-bottom:6px;">&#128194;</div>
59
+ Open / Import
60
+ <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">STEP, STL, Inventor, JSON</div>
61
+ </button>
62
+ <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;">
63
+ <div style="font-size:24px;margin-bottom:6px;">&#129302;</div>
64
+ Text-to-CAD
65
+ <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Describe a part in English</div>
66
+ </button>
67
+ <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;">
68
+ <div style="font-size:24px;margin-bottom:6px;">&#127981;</div>
69
+ Inventor Project
70
+ <div style="font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;">Load .ipj / .ipt / .iam</div>
71
+ </button>
72
+ </div>
73
+ <div style="color:#666;font-size:11px;">v0.9.0 &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>
74
+ </div>
75
+ </div>
76
+ <script>
77
+ window._dismissSplash = function(action) {
78
+ document.getElementById('welcome-panel').style.display = 'none';
79
+ if (action === 'sketch') {
80
+ // Will be handled by module script once loaded
81
+ window._pendingSplashAction = 'sketch-new';
82
+ } else if (action === 'import' || action === 'inventor') {
83
+ window._pendingSplashAction = 'file-import';
84
+ } else if (action === 'textcad') {
85
+ window._pendingSplashAction = 'tools-text-to-cad';
86
+ }
87
+ };
88
+ </script>
89
+
90
+ '''
91
+
92
+ # Insert before modal dialogs
93
+ content = content.replace('<!-- Modal Dialogs -->', new_splash + '<!-- Modal Dialogs -->')
94
+
95
+ # Add pending action handler inside the module script, right after menu handler definition
96
+ # Find the end of handleMenuAction and add a check for pending splash action
97
+ old_menu_end = " // ===== Workspace Switching ====="
98
+ new_menu_end = """ // ===== Handle pending splash action =====
99
+ if (window._pendingSplashAction) {
100
+ const pa = window._pendingSplashAction;
101
+ window._pendingSplashAction = null;
102
+ handleMenuAction(pa);
103
+ }
104
+ // Process splash actions dispatched after module load
105
+ Object.defineProperty(window, '_pendingSplashAction', {
106
+ set(v) { if (v) { setTimeout(() => handleMenuAction(v), 100); } },
107
+ get() { return null; },
108
+ configurable: true
109
+ });
110
+
111
+ // ===== Workspace Switching ====="""
112
+
113
+ content = content.replace(old_menu_end, new_menu_end, 1)
114
+
115
+ with open('app/index.html', 'w') as f:
116
+ f.write(content)
117
+
118
+ print('Splash v2 applied successfully!')
119
+ PYEOF
120
+
121
+ echo ""
122
+ echo "=== Done! Now run: ==="
123
+ echo " git add app/index.html && git commit -m 'Fix splash buttons with global onclick handlers' && git push origin main"
@@ -0,0 +1,181 @@
1
+ #!/bin/bash
2
+ # cycleCAD Fix: Welcome Splash + ViewCube + Dialog Selectors + Remove Demo Geometry
3
+ # Run from ~/cyclecad: bash fix-splash-viewcube.sh
4
+
5
+ set -e
6
+ cd "$(dirname "$0")"
7
+
8
+ echo "=== Fix 1: text-to-cad.js syntax error (line 215) ==="
9
+ sed -i.bak 's/ if \/(array|repeat|pattern)\/.test(lower)) return '\''pattern'\'';/ if (\/(array|repeat|pattern)\/.test(lower)) return '\''pattern'\'';/' app/js/modules/text-to-cad.js
10
+ rm -f app/js/modules/text-to-cad.js.bak
11
+
12
+ echo "=== Fix 2: Replace .dialog-content with #dialog-body in all 12 tool handlers ==="
13
+ sed -i.bak "s/document.querySelector('.dialog-content')/document.getElementById('dialog-body')/g" app/index.html
14
+ rm -f app/index.html.bak
15
+
16
+ echo "=== Fix 3: Remove demo geometry (box + cylinder) ==="
17
+ # Remove the demo geometry block between "Demo geometry" and "Ground plane"
18
+ python3 -c "
19
+ import re
20
+ with open('app/index.html', 'r') as f:
21
+ content = f.read()
22
+
23
+ # Remove demo geometry block
24
+ content = re.sub(
25
+ r' // Demo geometry.*?scene\.add\(cyl\);\n\n // Ground plane',
26
+ ' // Ground plane',
27
+ content,
28
+ flags=re.DOTALL
29
+ )
30
+ with open('app/index.html', 'w') as f:
31
+ f.write(content)
32
+ print('Demo geometry removed')
33
+ "
34
+
35
+ echo "=== Fix 4: Add welcome splash panel ==="
36
+ python3 -c "
37
+ with open('app/index.html', 'r') as f:
38
+ content = f.read()
39
+
40
+ splash = '''<!-- Welcome Splash Screen -->
41
+ <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;\">
42
+ <div style=\"background:var(--bg-secondary,#252526);border:1px solid var(--border-color,#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);\">
43
+ <div style=\"font-size:36px;margin-bottom:4px;\">
44
+ <span style=\"color:#0284C7;font-weight:700;\">cycle</span><span style=\"color:#e0e0e0;font-weight:300;\">CAD</span>
45
+ </div>
46
+ <div style=\"color:#888;font-size:13px;margin-bottom:28px;\">Agent-First Parametric 3D CAD Modeler</div>
47
+ <div style=\"display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px;\">
48
+ <button data-action=\"new-sketch\" style=\"background:#0284C7;color:#fff;border:none;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;\" onmouseover=\"this.style.background='#0369a1'\" onmouseout=\"this.style.background='#0284C7'\">
49
+ <div style=\"font-size:24px;margin-bottom:6px;\">&#9999;&#65039;</div>
50
+ New Sketch
51
+ <div style=\"font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;\">Start with a 2D sketch</div>
52
+ </button>
53
+ <button data-action=\"import\" style=\"background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;\" onmouseover=\"this.style.background='#4b5563'\" onmouseout=\"this.style.background='#374151'\">
54
+ <div style=\"font-size:24px;margin-bottom:6px;\">&#128194;</div>
55
+ Open / Import
56
+ <div style=\"font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;\">STEP, STL, Inventor, JSON</div>
57
+ </button>
58
+ <button data-action=\"ai-generate\" style=\"background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;\" onmouseover=\"this.style.background='#4b5563'\" onmouseout=\"this.style.background='#374151'\">
59
+ <div style=\"font-size:24px;margin-bottom:6px;\">&#129302;</div>
60
+ Text-to-CAD
61
+ <div style=\"font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;\">Describe a part in English</div>
62
+ </button>
63
+ <button data-action=\"load-inventor\" style=\"background:#374151;color:#fff;border:1px solid #4b5563;border-radius:8px;padding:18px 16px;cursor:pointer;font-size:14px;font-weight:600;\" onmouseover=\"this.style.background='#4b5563'\" onmouseout=\"this.style.background='#374151'\">
64
+ <div style=\"font-size:24px;margin-bottom:6px;\">&#127981;</div>
65
+ Inventor Project
66
+ <div style=\"font-size:11px;font-weight:400;color:rgba(255,255,255,0.7);margin-top:4px;\">Load .ipj / .ipt / .iam</div>
67
+ </button>
68
+ </div>
69
+ <div style=\"color:#666;font-size:11px;\">v0.9.0 &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>
70
+ </div>
71
+ </div>
72
+
73
+ '''
74
+
75
+ # Insert before modal dialogs
76
+ content = content.replace('<!-- Modal Dialogs -->', splash + '<!-- Modal Dialogs -->')
77
+ with open('app/index.html', 'w') as f:
78
+ f.write(content)
79
+ print('Welcome splash added')
80
+ "
81
+
82
+ echo "=== Fix 5: Add ViewCube to viewport ==="
83
+ python3 -c "
84
+ with open('app/index.html', 'r') as f:
85
+ content = f.read()
86
+
87
+ # Add position:relative and viewcube div to viewport-container
88
+ content = content.replace(
89
+ '<div id=\"viewport-container\">',
90
+ '<div id=\"viewport-container\" style=\"position:relative;\">'
91
+ )
92
+ content = content.replace(
93
+ '</canvas>\n </div>\n\n <!-- Right Panel',
94
+ '</canvas>\n <div id=\"viewcube\" style=\"position:absolute;top:12px;right:12px;width:90px;height:90px;pointer-events:auto;z-index:100;\"></div>\n </div>\n\n <!-- Right Panel'
95
+ )
96
+
97
+ # Add ViewCube JS before animation loop
98
+ viewcube_js = ''' // ===== ViewCube =====
99
+ const vcContainer = document.getElementById('viewcube');
100
+ const vcScene = new THREE.Scene();
101
+ const vcCamera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
102
+ vcCamera.position.set(0, 0, 3.5);
103
+ const vcRenderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
104
+ vcRenderer.setSize(90, 90);
105
+ vcRenderer.setPixelRatio(window.devicePixelRatio);
106
+ vcContainer.appendChild(vcRenderer.domElement);
107
+ const faceLabels = ['Right', 'Left', 'Top', 'Bottom', 'Front', 'Back'];
108
+ const faceColors = [0x3b82f6, 0x3b82f6, 0x10b981, 0x10b981, 0x0284C7, 0x0284C7];
109
+ const cubeMats = faceLabels.map((label, i) => {
110
+ const canvas = document.createElement('canvas');
111
+ canvas.width = 128; canvas.height = 128;
112
+ const ctx = canvas.getContext('2d');
113
+ ctx.fillStyle = '#' + faceColors[i].toString(16).padStart(6, '0');
114
+ ctx.fillRect(0, 0, 128, 128);
115
+ ctx.strokeStyle = '#1e293b'; ctx.lineWidth = 3; ctx.strokeRect(1, 1, 126, 126);
116
+ ctx.fillStyle = '#fff'; ctx.font = 'bold 22px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
117
+ ctx.fillText(label, 64, 64);
118
+ return new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(canvas) });
119
+ });
120
+ const vcCube = new THREE.Mesh(new THREE.BoxGeometry(1.6, 1.6, 1.6), cubeMats);
121
+ vcScene.add(vcCube);
122
+ const axLen = 1.3;
123
+ const axMat = (c) => new THREE.LineBasicMaterial({ color: c });
124
+ const axGeo = (v) => new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), v]);
125
+ vcScene.add(new THREE.Line(axGeo(new THREE.Vector3(axLen, 0, 0)), axMat(0xff4444)));
126
+ vcScene.add(new THREE.Line(axGeo(new THREE.Vector3(0, axLen, 0)), axMat(0x44ff44)));
127
+ vcScene.add(new THREE.Line(axGeo(new THREE.Vector3(0, 0, axLen)), axMat(0x4444ff)));
128
+ const vcViews = { Right:{x:3,y:0,z:0}, Left:{x:-3,y:0,z:0}, Top:{x:0,y:3,z:0}, Bottom:{x:0,y:-3,z:0}, Front:{x:0,y:0,z:3}, Back:{x:0,y:0,z:-3} };
129
+ const vcRay = new THREE.Raycaster(); const vcMouse = new THREE.Vector2();
130
+ vcRenderer.domElement.addEventListener('click', (e) => {
131
+ const rect = vcRenderer.domElement.getBoundingClientRect();
132
+ vcMouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
133
+ vcMouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
134
+ vcRay.setFromCamera(vcMouse, vcCamera);
135
+ const hits = vcRay.intersectObject(vcCube);
136
+ if (hits.length > 0) {
137
+ const v = vcViews[faceLabels[hits[0].face.materialIndex]];
138
+ if (v) { const s = camera.position.length() / 3; camera.position.set(v.x*s, v.y*s, v.z*s); camera.lookAt(controls.target); controls.update(); }
139
+ }
140
+ });
141
+ vcRenderer.domElement.style.cursor = 'pointer';
142
+
143
+ '''
144
+
145
+ content = content.replace(
146
+ ' // Animation loop\n function animate() {\n requestAnimationFrame(animate);\n controls.update();\n renderer.render(scene, camera);\n }',
147
+ viewcube_js + ' // Animation loop\n function animate() {\n requestAnimationFrame(animate);\n controls.update();\n renderer.render(scene, camera);\n vcCube.quaternion.copy(camera.quaternion).invert();\n vcRenderer.render(vcScene, vcCamera);\n }'
148
+ )
149
+
150
+ # Add welcome splash wiring before menu handler
151
+ splash_js = ''' // ===== Welcome Splash =====
152
+ const welcomePanel = document.getElementById('welcome-panel');
153
+ if (welcomePanel) {
154
+ const dismissWelcome = () => { welcomePanel.style.display = 'none'; };
155
+ const wb1 = welcomePanel.querySelector('[data-action=\"new-sketch\"]');
156
+ if (wb1) wb1.onclick = () => { dismissWelcome(); handleMenuAction('sketch-new'); };
157
+ const wb2 = welcomePanel.querySelector('[data-action=\"import\"]');
158
+ if (wb2) wb2.onclick = () => { dismissWelcome(); handleMenuAction('file-import'); };
159
+ const wb3 = welcomePanel.querySelector('[data-action=\"ai-generate\"]');
160
+ if (wb3) wb3.onclick = () => { dismissWelcome(); handleMenuAction('tools-text-to-cad'); };
161
+ const wb4 = welcomePanel.querySelector('[data-action=\"load-inventor\"]');
162
+ if (wb4) wb4.onclick = () => { dismissWelcome(); handleMenuAction('file-import'); };
163
+ }
164
+
165
+ '''
166
+
167
+ content = content.replace(' // ===== Menu Actions Handler =====', splash_js + ' // ===== Menu Actions Handler =====')
168
+
169
+ with open('app/index.html', 'w') as f:
170
+ f.write(content)
171
+ print('ViewCube + welcome wiring added')
172
+ "
173
+
174
+ echo ""
175
+ echo "=== All fixes applied! ==="
176
+ echo ""
177
+ echo "Now run:"
178
+ echo " git add app/index.html app/js/modules/text-to-cad.js"
179
+ echo " git commit -m 'Fix: welcome splash, ViewCube, dialog selectors, remove demo geometry'"
180
+ echo " git push origin main"
181
+ echo " npm version patch && npm publish"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyclecad",
3
- "version": "3.9.1",
3
+ "version": "3.9.3",
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": {