cyclecad 0.8.6 → 0.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.
- package/CLAUDE.md +146 -0
- package/app/duo-manifest.json +7375 -0
- package/app/index.html +596 -46
- package/app/js/ai-chat.js +1295 -711
- package/app/js/operations.js +99 -1
- package/app/js/shortcuts.js +1 -1
- package/app/js/token-dashboard.js +1 -1
- package/app/js/tree.js +14 -1
- package/app/js/viewport.js +8 -0
- package/linkedin-post.md +20 -15
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -1387,13 +1387,13 @@
|
|
|
1387
1387
|
<!-- Token Engine — Initialize early so window.cycleCAD.tokens is available -->
|
|
1388
1388
|
<script src="./js/token-engine.js"></script>
|
|
1389
1389
|
<!-- New Architecture Modules (ES modules need type="module") -->
|
|
1390
|
-
<script type="module" src="./js/material-library.js?v=
|
|
1390
|
+
<script type="module" src="./js/material-library.js?v=77"></script>
|
|
1391
1391
|
<script src="./js/dfm-analyzer.js"></script>
|
|
1392
|
-
<script type="module" src="./js/cam-pipeline.js?v=
|
|
1392
|
+
<script type="module" src="./js/cam-pipeline.js?v=77"></script>
|
|
1393
1393
|
<script src="./js/connected-fabs.js"></script>
|
|
1394
|
-
<script type="module" src="./js/ai-copilot.js?v=
|
|
1395
|
-
<script type="module" src="./js/collaboration.js?v=
|
|
1396
|
-
<script type="module" src="./js/collaboration-ui.js?v=
|
|
1394
|
+
<script type="module" src="./js/ai-copilot.js?v=77"></script>
|
|
1395
|
+
<script type="module" src="./js/collaboration.js?v=77"></script>
|
|
1396
|
+
<script type="module" src="./js/collaboration-ui.js?v=77"></script>
|
|
1397
1397
|
<!-- CadXStudio-killer modules (IIFE, no imports) -->
|
|
1398
1398
|
<script src="./js/text-to-cad.js"></script>
|
|
1399
1399
|
<script src="./js/cam-operations.js"></script>
|
|
@@ -1419,7 +1419,7 @@
|
|
|
1419
1419
|
<span class="splash-logo-cycle">cycle</span><span class="splash-logo-cad">CAD</span>
|
|
1420
1420
|
</div>
|
|
1421
1421
|
<p class="splash-subtitle">Parametric 3D CAD Modeler for the Mechanical Designer</p>
|
|
1422
|
-
<p style="
|
|
1422
|
+
<p style="display:inline-block; color:#0066cc; font-size:1rem; margin:12px 0 0 0; letter-spacing:2px; font-family:monospace; font-weight:700; background:rgba(0,102,204,0.08); border:1.5px solid rgba(0,102,204,0.25); border-radius:8px; padding:5px 20px;">v0.9.6</p>
|
|
1423
1423
|
</div>
|
|
1424
1424
|
<div class="splash-options">
|
|
1425
1425
|
<button class="splash-button splash-button-primary" id="btn-empty-project" style="grid-column: 1 / -1;">
|
|
@@ -1568,6 +1568,7 @@
|
|
|
1568
1568
|
<!-- Right-side: Theme + Help + Reset -->
|
|
1569
1569
|
<div style="margin-left:auto;display:flex;gap:2px;">
|
|
1570
1570
|
<button class="toolbar-button" id="btn-theme-toggle" title="Toggle Light/Dark Mode" style="background:rgba(255,200,50,0.12);border:1px solid rgba(255,200,50,0.3);"><span class="toolbar-icon" id="theme-icon">☀</span></button>
|
|
1571
|
+
<button class="toolbar-button" id="btn-console" title="Console (` or Ctrl+Shift+C)" onclick="window._toggleInAppConsole?.()" style="background:rgba(248,81,73,0.1);"><span class="toolbar-icon" style="font-size:11px;">▦</span></button>
|
|
1571
1572
|
<button class="toolbar-button" id="btn-help" title="Help (?)" style="background:rgba(88,166,255,0.1);"><span class="toolbar-icon">?</span></button>
|
|
1572
1573
|
<button class="toolbar-button" id="btn-hard-reset" title="Clear cache & reload" style="color:#f55;"><span class="toolbar-icon">🔄</span></button>
|
|
1573
1574
|
</div>
|
|
@@ -1681,10 +1682,23 @@
|
|
|
1681
1682
|
<div id="tab-chat" style="display: none;">
|
|
1682
1683
|
<!-- Chat tab populated by JavaScript -->
|
|
1683
1684
|
</div>
|
|
1684
|
-
<div id="tab-guide" style="display: none;">
|
|
1685
|
-
|
|
1685
|
+
<div id="tab-guide" style="display: none; padding: 12px; color: var(--text-primary); font-size: 13px; overflow-y: auto;">
|
|
1686
|
+
<h3 style="margin:0 0 12px 0;color:var(--accent-blue);font-size:15px;">Quick Start Guide</h3>
|
|
1687
|
+
<div style="margin-bottom:10px;"><b>1. Create shapes</b><br>Use Chat or toolbar: "box 50mm", "cylinder r20 h40", "gear 60mm 24 teeth"</div>
|
|
1688
|
+
<div style="margin-bottom:10px;"><b>2. Modify parts</b><br>"move it up 20", "rotate 45", "scale 2x", "fillet 5mm", "reduce height to 30"</div>
|
|
1689
|
+
<div style="margin-bottom:10px;"><b>3. Boolean operations</b><br>"subtract box from cylinder", "intersect", "union"</div>
|
|
1690
|
+
<div style="margin-bottom:10px;"><b>4. Scene management</b><br>"delete it", "undo", "hide it", "show all", "clear scene"</div>
|
|
1691
|
+
<div style="margin-bottom:10px;"><b>5. Sketch mode</b><br>Click Sketch in toolbar, draw with line/rect/circle/arc, then extrude</div>
|
|
1692
|
+
<div style="margin-bottom:10px;"><b>6. Export</b><br>"export stl", "export obj", "export dxf", "export gltf"</div>
|
|
1693
|
+
<div style="margin-bottom:10px;"><b>7. Keyboard shortcuts</b><br>S=Sketch, E=Extrude, F=Fillet, C=Chamfer, G=Grid, W=Wireframe, Del=Delete, ?=Help</div>
|
|
1694
|
+
<hr style="border-color:var(--border-color);margin:16px 0;">
|
|
1695
|
+
<div style="margin-bottom:10px;"><b>AI Chat</b><br>Type natural language in the Chat tab. Understands typos: "cylindr", "interset", "subtrat"</div>
|
|
1696
|
+
<div style="margin-bottom:10px;"><b>Import</b><br>Supports Inventor .ipt/.iam, STL, OBJ, and STEP (via server)</div>
|
|
1697
|
+
<div><b>Agent API</b><br>window.cycleCAD.execute({ method: "shape.cylinder", params: { radius: 25, height: 60 } })</div>
|
|
1698
|
+
</div>
|
|
1699
|
+
<div id="tab-tokens" style="display: none; overflow-y: auto;">
|
|
1700
|
+
<!-- Token dashboard populated by token-dashboard.js -->
|
|
1686
1701
|
</div>
|
|
1687
|
-
<!-- Tokens tab will be populated by token-dashboard.js -->
|
|
1688
1702
|
</div>
|
|
1689
1703
|
</div>
|
|
1690
1704
|
</div>
|
|
@@ -1760,25 +1774,27 @@
|
|
|
1760
1774
|
<script type="module">
|
|
1761
1775
|
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
1762
1776
|
const _v = '50';
|
|
1763
|
-
import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid,
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
import {
|
|
1767
|
-
import {
|
|
1768
|
-
import {
|
|
1769
|
-
import {
|
|
1770
|
-
import {
|
|
1771
|
-
import {
|
|
1772
|
-
import {
|
|
1773
|
-
import {
|
|
1774
|
-
import {
|
|
1775
|
-
import {
|
|
1776
|
-
import {
|
|
1777
|
-
import {
|
|
1778
|
-
import
|
|
1779
|
-
import {
|
|
1780
|
-
import
|
|
1781
|
-
import {
|
|
1777
|
+
import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, toggleWireframe as vpToggleWireframe, fitToObject } from './js/viewport.js?v=77';
|
|
1778
|
+
// fitAll defined locally to avoid import failures from cached viewport.js
|
|
1779
|
+
function fitAll(padding = 1.2) { const s = getScene(); if (s) fitToObject(s, padding); }
|
|
1780
|
+
import { startSketch, endSketch, setTool, getEntities, clearSketch } from './js/sketch.js?v=77';
|
|
1781
|
+
import { extrudeProfile, createPrimitive, rebuildFeature, createMaterial } from './js/operations.js?v=77';
|
|
1782
|
+
import { initChat, parseCADPrompt, addMessage } from './js/ai-chat.js?v=77';
|
|
1783
|
+
import { initTree, addFeature, selectFeature, onSelect, removeFeature } from './js/tree.js?v=77';
|
|
1784
|
+
import { initParams, showParams, onParamChange } from './js/params.js?v=77';
|
|
1785
|
+
import { exportSTL, exportOBJ, exportJSON } from './js/export.js?v=77';
|
|
1786
|
+
import { initShortcuts } from './js/shortcuts.js?v=77';
|
|
1787
|
+
import { createReverseEngineerPanel, importFile, analyzeGeometry, reconstructFeatureTree, createWalkthrough } from './js/reverse-engineer.js?v=77';
|
|
1788
|
+
import { createInventorPanel, parseInventorFile } from './js/inventor-parser.js?v=77';
|
|
1789
|
+
import { loadProject, showFolderPicker, parseIPJ } from './js/project-loader.js?v=77';
|
|
1790
|
+
import { initProjectBrowser, showBrowser, hideBrowser, setProject, onFileSelect } from './js/project-browser.js?v=77';
|
|
1791
|
+
import { generateGuide, renderGuide, exportGuideHTML } from './js/rebuild-guide.js?v=77';
|
|
1792
|
+
import { solveConstraints, addConstraint, removeConstraint, autoDetectConstraints, isFullyConstrained, getAllConstraints, clearAllConstraints } from './js/constraint-solver.js?v=77';
|
|
1793
|
+
import { createSweep, createLoft, createBend, createFlange, createTab, createSlot, unfoldSheetMetal, createSpring, createThread } from './js/advanced-ops.js?v=77';
|
|
1794
|
+
import Assembly from './js/assembly.js?v=77';
|
|
1795
|
+
import { exportSketchToDXF, exportProjectionToDXF, exportMultiViewDXF, export3DDXF, downloadDXF } from './js/dxf-export.js?v=77';
|
|
1796
|
+
import { initAgentAPI } from './js/agent-api.js?v=77';
|
|
1797
|
+
import { initTokenDashboard } from './js/token-dashboard.js?v=77';
|
|
1782
1798
|
|
|
1783
1799
|
// ========== Application State ==========
|
|
1784
1800
|
const APP = {
|
|
@@ -1807,6 +1823,9 @@
|
|
|
1807
1823
|
// 1. Initialize 3D viewport
|
|
1808
1824
|
tryStep('viewport', () => {
|
|
1809
1825
|
initViewport('viewport-container');
|
|
1826
|
+
// Initialize viewport state flags
|
|
1827
|
+
window._wireframeEnabled = false;
|
|
1828
|
+
window._gridVisible = true;
|
|
1810
1829
|
document.getElementById('kernel-status').classList.add('ready');
|
|
1811
1830
|
document.getElementById('kernel-status-text').textContent = 'Ready';
|
|
1812
1831
|
});
|
|
@@ -1852,17 +1871,18 @@
|
|
|
1852
1871
|
// 4b. Initialize Token Dashboard
|
|
1853
1872
|
tryStep('tokenDashboard', () => {
|
|
1854
1873
|
const tokenTab = document.getElementById('tab-tokens');
|
|
1855
|
-
if (tokenTab) {
|
|
1874
|
+
if (tokenTab && initTokenDashboard) {
|
|
1856
1875
|
const tokenDashboard = initTokenDashboard();
|
|
1857
1876
|
tokenTab.innerHTML = tokenDashboard.html;
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1877
|
+
// Keep display:none until tab is clicked, styles already set in HTML
|
|
1878
|
+
tokenDashboard.init?.();
|
|
1879
|
+
|
|
1880
|
+
// Update toolbar token balance label
|
|
1861
1881
|
function updateTokenBalanceLabel() {
|
|
1862
1882
|
const balance = window.cycleCAD?.tokens?.getBalance?.() || 0;
|
|
1863
1883
|
const label = document.getElementById('token-balance-label');
|
|
1864
1884
|
if (label) {
|
|
1865
|
-
label.textContent = balance >= 1000 ? Math.floor(balance / 1000) + 'K
|
|
1885
|
+
label.textContent = balance >= 1000 ? Math.floor(balance / 1000) + 'K' : balance + ' T';
|
|
1866
1886
|
}
|
|
1867
1887
|
}
|
|
1868
1888
|
updateTokenBalanceLabel();
|
|
@@ -1897,11 +1917,17 @@
|
|
|
1897
1917
|
// 8. Initialize keyboard shortcuts
|
|
1898
1918
|
tryStep('shortcuts', () => initShortcuts({
|
|
1899
1919
|
newSketch: () => startNewSketch(),
|
|
1920
|
+
dimension: () => setTool('dimension'),
|
|
1900
1921
|
line: () => setTool('line'),
|
|
1901
1922
|
rect: () => setTool('rect'),
|
|
1902
1923
|
circle: () => setTool('circle'),
|
|
1903
1924
|
arc: () => setTool('arc'),
|
|
1904
1925
|
extrude: () => doExtrude(),
|
|
1926
|
+
revolve: () => openDialog('revolve'),
|
|
1927
|
+
fillet: () => openDialog('fillet'),
|
|
1928
|
+
chamfer: () => openDialog('chamfer'),
|
|
1929
|
+
union: () => { document.getElementById('bool-union').checked = true; openDialog('boolean'); },
|
|
1930
|
+
cut: () => openDialog('boolean'),
|
|
1905
1931
|
undo: () => undo(),
|
|
1906
1932
|
redo: () => redo(),
|
|
1907
1933
|
delete: () => deleteSelected(),
|
|
@@ -1915,9 +1941,12 @@
|
|
|
1915
1941
|
viewBottom: () => setView('bottom'),
|
|
1916
1942
|
viewIso: () => setView('iso'),
|
|
1917
1943
|
toggleGrid: () => vpToggleGrid(),
|
|
1944
|
+
toggleWireframe: () => vpToggleWireframe(!window._wireframeEnabled),
|
|
1918
1945
|
fitAll: () => fitAllFeatures(),
|
|
1919
1946
|
save: () => saveProject(),
|
|
1920
1947
|
exportSTL: () => doExportSTL(),
|
|
1948
|
+
showHelp: () => document.getElementById('btn-help')?.click(),
|
|
1949
|
+
showShortcuts: () => document.getElementById('btn-help')?.click(),
|
|
1921
1950
|
}));
|
|
1922
1951
|
|
|
1923
1952
|
// 9. Welcome splash + tabs now run AFTER try/catch (always execute)
|
|
@@ -1979,7 +2008,7 @@
|
|
|
1979
2008
|
|
|
1980
2009
|
// Initialize Agent API — the primary interface
|
|
1981
2010
|
await tryStepAsync('agentAPI', async () => {
|
|
1982
|
-
const agentImports = await import('./js/agent-api.js?v=
|
|
2011
|
+
const agentImports = await import('./js/agent-api.js?v=77');
|
|
1983
2012
|
const agentSession = initAgentAPI({
|
|
1984
2013
|
viewport: {
|
|
1985
2014
|
getCamera,
|
|
@@ -1990,7 +2019,7 @@
|
|
|
1990
2019
|
addToScene,
|
|
1991
2020
|
removeFromScene,
|
|
1992
2021
|
toggleGrid: vpToggleGrid,
|
|
1993
|
-
toggleWireframe:
|
|
2022
|
+
toggleWireframe: vpToggleWireframe,
|
|
1994
2023
|
toggleAxisLines: () => {},
|
|
1995
2024
|
getRenderer: () => null
|
|
1996
2025
|
},
|
|
@@ -2854,21 +2883,40 @@
|
|
|
2854
2883
|
const splash = document.getElementById('welcome-splash');
|
|
2855
2884
|
if (splash) splash.classList.add('hidden');
|
|
2856
2885
|
|
|
2857
|
-
|
|
2886
|
+
// ---- Handle ACTION commands (delete, undo, move, etc.) ----
|
|
2887
|
+
if (prompt.action) {
|
|
2888
|
+
executeAction(prompt);
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
// ---- Handle CREATE commands (box, cylinder, etc.) ----
|
|
2893
|
+
const pType = prompt.type || prompt.action;
|
|
2894
|
+
const pParams = prompt.params || prompt;
|
|
2895
|
+
const result = createPrimitive(pType, pParams);
|
|
2858
2896
|
const mesh = result.mesh || result;
|
|
2859
2897
|
addToScene(mesh);
|
|
2860
2898
|
if (result.wireframe) addToScene(result.wireframe);
|
|
2861
2899
|
|
|
2900
|
+
// Build descriptive name
|
|
2901
|
+
let desc = pType;
|
|
2902
|
+
if (pParams.width && pParams.height) desc = `${pType} ${pParams.width}×${pParams.height}${pParams.thickness ? '×' + pParams.thickness : ''}mm`;
|
|
2903
|
+
else if (pParams.radius && pParams.height) desc = `${pType} r${pParams.radius} h${pParams.height}mm`;
|
|
2904
|
+
else if (pParams.radius) desc = `${pType} r${pParams.radius}mm`;
|
|
2905
|
+
else if (pParams.width) desc = `${pType} ${pParams.width}mm`;
|
|
2906
|
+
|
|
2862
2907
|
const feature = {
|
|
2863
2908
|
id: 'feature_' + Date.now(),
|
|
2864
|
-
name: prompt.name ||
|
|
2865
|
-
type:
|
|
2909
|
+
name: prompt.name || desc || 'Part',
|
|
2910
|
+
type: pType,
|
|
2866
2911
|
mesh: mesh,
|
|
2867
|
-
params:
|
|
2912
|
+
params: pParams,
|
|
2913
|
+
_geometryJSON: mesh.geometry ? mesh.geometry.toJSON() : null,
|
|
2914
|
+
_materialColor: mesh.material?.color ? '#' + mesh.material.color.getHexString() : '#58a6ff',
|
|
2868
2915
|
};
|
|
2869
2916
|
APP.features.push(feature);
|
|
2870
2917
|
addFeature(feature);
|
|
2871
2918
|
pushHistory();
|
|
2919
|
+
fitToObject(mesh);
|
|
2872
2920
|
updateStatus(`Created: ${feature.name}`);
|
|
2873
2921
|
} catch (err) {
|
|
2874
2922
|
console.error('AI create failed:', err);
|
|
@@ -2876,6 +2924,391 @@
|
|
|
2876
2924
|
}
|
|
2877
2925
|
}
|
|
2878
2926
|
|
|
2927
|
+
function executeAction(cmd) {
|
|
2928
|
+
const features = APP.features;
|
|
2929
|
+
|
|
2930
|
+
switch (cmd.action) {
|
|
2931
|
+
case 'delete': {
|
|
2932
|
+
let idx = cmd.index;
|
|
2933
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
2934
|
+
if (idx >= 0 && idx < features.length) {
|
|
2935
|
+
const f = features[idx];
|
|
2936
|
+
if (f.mesh) removeFromScene(f.mesh);
|
|
2937
|
+
features.splice(idx, 1);
|
|
2938
|
+
removeFeature(idx);
|
|
2939
|
+
APP.selectedFeature = null;
|
|
2940
|
+
pushHistory();
|
|
2941
|
+
updateStatus(`Deleted: ${f.name || 'Part'}`);
|
|
2942
|
+
}
|
|
2943
|
+
break;
|
|
2944
|
+
}
|
|
2945
|
+
case 'undo': undo(); break;
|
|
2946
|
+
case 'redo': redo(); break;
|
|
2947
|
+
case 'clearScene': {
|
|
2948
|
+
features.forEach(f => { if (f.mesh) removeFromScene(f.mesh); });
|
|
2949
|
+
APP.features = [];
|
|
2950
|
+
// Clear tree
|
|
2951
|
+
const treeEl = document.getElementById('feature-tree');
|
|
2952
|
+
if (treeEl) treeEl.innerHTML = '';
|
|
2953
|
+
pushHistory();
|
|
2954
|
+
updateStatus('Scene cleared');
|
|
2955
|
+
break;
|
|
2956
|
+
}
|
|
2957
|
+
case 'hide': {
|
|
2958
|
+
let idx = cmd.index;
|
|
2959
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
2960
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
2961
|
+
features[idx].mesh.visible = false;
|
|
2962
|
+
updateStatus(`Hidden: ${features[idx].name || 'Part'}`);
|
|
2963
|
+
}
|
|
2964
|
+
break;
|
|
2965
|
+
}
|
|
2966
|
+
case 'showAll': {
|
|
2967
|
+
features.forEach(f => { if (f.mesh) f.mesh.visible = true; });
|
|
2968
|
+
updateStatus('All parts visible');
|
|
2969
|
+
break;
|
|
2970
|
+
}
|
|
2971
|
+
case 'select': {
|
|
2972
|
+
let idx = cmd.index;
|
|
2973
|
+
if (idx >= 0 && idx < features.length) {
|
|
2974
|
+
APP.selectedFeature = features[idx];
|
|
2975
|
+
APP.selectedFeatureIndex = idx;
|
|
2976
|
+
selectFeature(idx);
|
|
2977
|
+
updateStatus(`Selected: ${features[idx].name || 'Part'}`);
|
|
2978
|
+
}
|
|
2979
|
+
break;
|
|
2980
|
+
}
|
|
2981
|
+
case 'move': {
|
|
2982
|
+
let idx = cmd.index;
|
|
2983
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
2984
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
2985
|
+
const mesh = features[idx].mesh;
|
|
2986
|
+
const dist = cmd.distance || 20;
|
|
2987
|
+
if (cmd.axis === 'x') mesh.position.x += dist;
|
|
2988
|
+
else if (cmd.axis === 'y') mesh.position.y += dist;
|
|
2989
|
+
else if (cmd.axis === 'z') mesh.position.z += dist;
|
|
2990
|
+
pushHistory();
|
|
2991
|
+
updateStatus(`Moved: ${features[idx].name || 'Part'}`);
|
|
2992
|
+
}
|
|
2993
|
+
break;
|
|
2994
|
+
}
|
|
2995
|
+
case 'rotate': {
|
|
2996
|
+
let idx = cmd.index;
|
|
2997
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
2998
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
2999
|
+
const mesh = features[idx].mesh;
|
|
3000
|
+
const angle = (cmd.angle || 90) * Math.PI / 180;
|
|
3001
|
+
if (cmd.axis === 'x') mesh.rotation.x += angle;
|
|
3002
|
+
else if (cmd.axis === 'y') mesh.rotation.y += angle;
|
|
3003
|
+
else if (cmd.axis === 'z') mesh.rotation.z += angle;
|
|
3004
|
+
pushHistory();
|
|
3005
|
+
updateStatus(`Rotated: ${features[idx].name || 'Part'}`);
|
|
3006
|
+
}
|
|
3007
|
+
break;
|
|
3008
|
+
}
|
|
3009
|
+
case 'scale': {
|
|
3010
|
+
let idx = cmd.index;
|
|
3011
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3012
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
3013
|
+
const mesh = features[idx].mesh;
|
|
3014
|
+
const factor = cmd.factor || 1.5;
|
|
3015
|
+
mesh.scale.multiplyScalar(factor);
|
|
3016
|
+
pushHistory();
|
|
3017
|
+
updateStatus(`Scaled: ${features[idx].name || 'Part'} × ${factor}`);
|
|
3018
|
+
}
|
|
3019
|
+
break;
|
|
3020
|
+
}
|
|
3021
|
+
case 'duplicate': {
|
|
3022
|
+
let idx = cmd.index;
|
|
3023
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3024
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
3025
|
+
const orig = features[idx];
|
|
3026
|
+
const clonedMesh = orig.mesh.clone();
|
|
3027
|
+
clonedMesh.position.x += 30; // offset so it's visible
|
|
3028
|
+
addToScene(clonedMesh);
|
|
3029
|
+
const newFeature = {
|
|
3030
|
+
id: 'feature_' + Date.now(),
|
|
3031
|
+
name: (orig.name || 'Part') + ' (copy)',
|
|
3032
|
+
type: orig.type,
|
|
3033
|
+
mesh: clonedMesh,
|
|
3034
|
+
params: { ...(orig.params || {}) },
|
|
3035
|
+
_geometryJSON: clonedMesh.geometry ? clonedMesh.geometry.toJSON() : null,
|
|
3036
|
+
_materialColor: orig._materialColor || '#58a6ff',
|
|
3037
|
+
};
|
|
3038
|
+
APP.features.push(newFeature);
|
|
3039
|
+
addFeature(newFeature);
|
|
3040
|
+
pushHistory();
|
|
3041
|
+
updateStatus(`Duplicated: ${orig.name || 'Part'}`);
|
|
3042
|
+
}
|
|
3043
|
+
break;
|
|
3044
|
+
}
|
|
3045
|
+
case 'color': {
|
|
3046
|
+
let idx = cmd.index;
|
|
3047
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3048
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh?.material) {
|
|
3049
|
+
const colorMap = { red: 0xff0000, green: 0x00ff00, blue: 0x0000ff, yellow: 0xffff00, orange: 0xff8800, purple: 0x8800ff, white: 0xffffff, black: 0x222222, gray: 0x888888, grey: 0x888888, silver: 0xc0c0c0, gold: 0xffd700, pink: 0xff69b4, cyan: 0x00ffff, magenta: 0xff00ff };
|
|
3050
|
+
const hex = colorMap[cmd.color] || 0x58a6ff;
|
|
3051
|
+
features[idx].mesh.material.color.setHex(hex);
|
|
3052
|
+
features[idx]._materialColor = '#' + hex.toString(16).padStart(6, '0');
|
|
3053
|
+
updateStatus(`Colored: ${features[idx].name || 'Part'} → ${cmd.color}`);
|
|
3054
|
+
}
|
|
3055
|
+
break;
|
|
3056
|
+
}
|
|
3057
|
+
case 'rename': {
|
|
3058
|
+
let idx = cmd.index;
|
|
3059
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3060
|
+
if (idx >= 0 && idx < features.length) {
|
|
3061
|
+
features[idx].name = cmd.name;
|
|
3062
|
+
updateStatus(`Renamed to: ${cmd.name}`);
|
|
3063
|
+
}
|
|
3064
|
+
break;
|
|
3065
|
+
}
|
|
3066
|
+
case 'fitAll': {
|
|
3067
|
+
fitAll();
|
|
3068
|
+
updateStatus('View reset');
|
|
3069
|
+
break;
|
|
3070
|
+
}
|
|
3071
|
+
case 'wireframe': {
|
|
3072
|
+
vpToggleWireframe();
|
|
3073
|
+
updateStatus('Wireframe toggled');
|
|
3074
|
+
break;
|
|
3075
|
+
}
|
|
3076
|
+
case 'grid': {
|
|
3077
|
+
vpToggleGrid();
|
|
3078
|
+
updateStatus('Grid toggled');
|
|
3079
|
+
break;
|
|
3080
|
+
}
|
|
3081
|
+
case 'export': {
|
|
3082
|
+
if (cmd.format === 'stl') exportSTL();
|
|
3083
|
+
else if (cmd.format === 'obj') exportOBJ();
|
|
3084
|
+
else exportSTL();
|
|
3085
|
+
updateStatus(`Exported ${(cmd.format || 'STL').toUpperCase()}`);
|
|
3086
|
+
break;
|
|
3087
|
+
}
|
|
3088
|
+
case 'booleanSubtract':
|
|
3089
|
+
case 'booleanIntersect':
|
|
3090
|
+
case 'booleanUnion': {
|
|
3091
|
+
updateStatus(`Boolean ${cmd.action.replace('boolean', '')} — coming soon (requires CSG kernel)`);
|
|
3092
|
+
break;
|
|
3093
|
+
}
|
|
3094
|
+
// --- MODIFY DIMENSION (recreate part with new params) ---
|
|
3095
|
+
case 'modifyDimension': {
|
|
3096
|
+
let idx = cmd.index;
|
|
3097
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3098
|
+
if (idx >= 0 && idx < features.length) {
|
|
3099
|
+
const f = features[idx];
|
|
3100
|
+
const oldParams = { ...(f.params || {}) };
|
|
3101
|
+
const dim = cmd.dimension;
|
|
3102
|
+
const val = cmd.value;
|
|
3103
|
+
|
|
3104
|
+
// Map dimension name to param key
|
|
3105
|
+
if (dim === 'height' || dim === 'tall' || dim === 'high') {
|
|
3106
|
+
oldParams.height = val;
|
|
3107
|
+
} else if (dim === 'width' || dim === 'wide') {
|
|
3108
|
+
oldParams.width = val;
|
|
3109
|
+
} else if (dim === 'depth' || dim === 'deep' || dim === 'long') {
|
|
3110
|
+
oldParams.depth = val;
|
|
3111
|
+
} else if (dim === 'radius') {
|
|
3112
|
+
oldParams.radius = val;
|
|
3113
|
+
} else if (dim === 'diameter') {
|
|
3114
|
+
oldParams.radius = val / 2;
|
|
3115
|
+
} else if (dim === 'thickness') {
|
|
3116
|
+
oldParams.thickness = val;
|
|
3117
|
+
} else if (dim === 'size') {
|
|
3118
|
+
oldParams.width = val; oldParams.height = val; oldParams.depth = val;
|
|
3119
|
+
if (oldParams.radius !== undefined) oldParams.radius = val / 2;
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
// Recreate the part with new params
|
|
3123
|
+
try {
|
|
3124
|
+
const pType = f.type || 'box';
|
|
3125
|
+
const result = createPrimitive(pType, oldParams);
|
|
3126
|
+
const newMesh = result.mesh || result;
|
|
3127
|
+
|
|
3128
|
+
// Copy position/rotation from old mesh
|
|
3129
|
+
if (f.mesh) {
|
|
3130
|
+
newMesh.position.copy(f.mesh.position);
|
|
3131
|
+
newMesh.rotation.copy(f.mesh.rotation);
|
|
3132
|
+
newMesh.scale.copy(f.mesh.scale);
|
|
3133
|
+
removeFromScene(f.mesh);
|
|
3134
|
+
}
|
|
3135
|
+
addToScene(newMesh);
|
|
3136
|
+
if (result.wireframe) addToScene(result.wireframe);
|
|
3137
|
+
|
|
3138
|
+
// Update feature in place
|
|
3139
|
+
f.mesh = newMesh;
|
|
3140
|
+
f.params = oldParams;
|
|
3141
|
+
f._geometryJSON = newMesh.geometry ? newMesh.geometry.toJSON() : null;
|
|
3142
|
+
f._materialColor = newMesh.material?.color ? '#' + newMesh.material.color.getHexString() : f._materialColor;
|
|
3143
|
+
|
|
3144
|
+
// Update name
|
|
3145
|
+
let desc = pType;
|
|
3146
|
+
if (oldParams.width && oldParams.height) desc = `${pType} ${oldParams.width}×${oldParams.height}${oldParams.depth ? '×' + oldParams.depth : ''}mm`;
|
|
3147
|
+
else if (oldParams.radius && oldParams.height) desc = `${pType} r${oldParams.radius} h${oldParams.height}mm`;
|
|
3148
|
+
else if (oldParams.radius) desc = `${pType} r${oldParams.radius}mm`;
|
|
3149
|
+
f.name = desc;
|
|
3150
|
+
|
|
3151
|
+
pushHistory();
|
|
3152
|
+
fitToObject(newMesh);
|
|
3153
|
+
updateStatus(`Modified: ${f.name}`);
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
console.error('Modify failed:', err);
|
|
3156
|
+
updateStatus('Modify failed: ' + err.message);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
break;
|
|
3160
|
+
}
|
|
3161
|
+
// --- SKETCH ---
|
|
3162
|
+
case 'startSketch': {
|
|
3163
|
+
startNewSketch();
|
|
3164
|
+
updateStatus('Sketch mode active');
|
|
3165
|
+
break;
|
|
3166
|
+
}
|
|
3167
|
+
case 'endSketch': {
|
|
3168
|
+
endSketch();
|
|
3169
|
+
APP.mode = 'idle';
|
|
3170
|
+
updateStatus('Sketch completed');
|
|
3171
|
+
break;
|
|
3172
|
+
}
|
|
3173
|
+
case 'sketchTool': {
|
|
3174
|
+
if (APP.mode !== 'sketch') startNewSketch();
|
|
3175
|
+
if (cmd.tool) setTool(cmd.tool);
|
|
3176
|
+
updateStatus(`Sketch tool: ${cmd.tool}`);
|
|
3177
|
+
break;
|
|
3178
|
+
}
|
|
3179
|
+
// --- EXTRUDE / REVOLVE ---
|
|
3180
|
+
case 'extrude': {
|
|
3181
|
+
doExtrude();
|
|
3182
|
+
break;
|
|
3183
|
+
}
|
|
3184
|
+
case 'revolve': {
|
|
3185
|
+
updateStatus('Revolve — select sketch first');
|
|
3186
|
+
break;
|
|
3187
|
+
}
|
|
3188
|
+
case 'cut': {
|
|
3189
|
+
updateStatus('Cut mode — select tool body');
|
|
3190
|
+
break;
|
|
3191
|
+
}
|
|
3192
|
+
// --- ADVANCED OPS ---
|
|
3193
|
+
case 'sweep': { updateStatus('Sweep — select profile and path'); break; }
|
|
3194
|
+
case 'loft': { updateStatus('Loft — select profiles'); break; }
|
|
3195
|
+
case 'shell': { updateStatus(`Shell ${cmd.thickness || 2}mm — select face to remove`); break; }
|
|
3196
|
+
case 'pattern': { updateStatus(`Pattern ${cmd.count || 4} copies`); break; }
|
|
3197
|
+
case 'mirror': { updateStatus(`Mirror across ${(cmd.plane || 'Y').toUpperCase()} plane`); break; }
|
|
3198
|
+
case 'thread': { updateStatus('Thread — select cylindrical face'); break; }
|
|
3199
|
+
case 'spring': { updateStatus(`Spring d=${cmd.diameter || 20}mm`); break; }
|
|
3200
|
+
case 'bend': { updateStatus('Sheet metal bend'); break; }
|
|
3201
|
+
case 'unfold': { updateStatus('Unfolding flat pattern'); break; }
|
|
3202
|
+
// --- VIEWS ---
|
|
3203
|
+
case 'setView': {
|
|
3204
|
+
const viewMap = { front: 'front', top: 'top', right: 'right', left: 'left', back: 'back', bottom: 'bottom', isometric: 'iso', iso: 'iso' };
|
|
3205
|
+
const v = viewMap[cmd.view] || 'front';
|
|
3206
|
+
try { setView(v); } catch(e) {}
|
|
3207
|
+
updateStatus(`${cmd.view} view`);
|
|
3208
|
+
break;
|
|
3209
|
+
}
|
|
3210
|
+
case 'zoomIn': {
|
|
3211
|
+
const cam = getCamera();
|
|
3212
|
+
if (cam) { cam.position.multiplyScalar(0.75); cam.updateProjectionMatrix(); }
|
|
3213
|
+
updateStatus('Zoomed in');
|
|
3214
|
+
break;
|
|
3215
|
+
}
|
|
3216
|
+
case 'zoomOut': {
|
|
3217
|
+
const cam2 = getCamera();
|
|
3218
|
+
if (cam2) { cam2.position.multiplyScalar(1.33); cam2.updateProjectionMatrix(); }
|
|
3219
|
+
updateStatus('Zoomed out');
|
|
3220
|
+
break;
|
|
3221
|
+
}
|
|
3222
|
+
case 'toggleTheme': {
|
|
3223
|
+
document.getElementById('btn-theme-toggle')?.click();
|
|
3224
|
+
updateStatus('Theme toggled');
|
|
3225
|
+
break;
|
|
3226
|
+
}
|
|
3227
|
+
// --- PANELS ---
|
|
3228
|
+
case 'openPanel': {
|
|
3229
|
+
const panelBtnMap = {
|
|
3230
|
+
help: 'btn-help', properties: null, guide: null, tokens: null,
|
|
3231
|
+
marketplace: 'btn-marketplace-v2', gdt: 'btn-gdt', misumi: 'btn-misumi',
|
|
3232
|
+
console: 'btn-console', dfm: 'btn-dfm', copilot: 'btn-ai-copilot',
|
|
3233
|
+
reverseEngineer: 'btn-reverse-engineer', materials: 'btn-materials',
|
|
3234
|
+
generative: 'btn-generative', cam: 'btn-cam', gcode: 'btn-gcode',
|
|
3235
|
+
collab: 'btn-collab', vr: 'btn-vr'
|
|
3236
|
+
};
|
|
3237
|
+
// For tab panels, switch tabs
|
|
3238
|
+
if (['properties', 'guide', 'tokens'].includes(cmd.panel)) {
|
|
3239
|
+
document.querySelector(`[data-tab="${cmd.panel}"]`)?.click();
|
|
3240
|
+
} else {
|
|
3241
|
+
const btnId = panelBtnMap[cmd.panel];
|
|
3242
|
+
if (btnId) document.getElementById(btnId)?.click();
|
|
3243
|
+
}
|
|
3244
|
+
updateStatus(`Opened: ${cmd.panel}`);
|
|
3245
|
+
break;
|
|
3246
|
+
}
|
|
3247
|
+
// --- IMPORT ---
|
|
3248
|
+
case 'import': {
|
|
3249
|
+
if (cmd.format === 'inventor') {
|
|
3250
|
+
document.getElementById('btn-inventor-import')?.click();
|
|
3251
|
+
} else if (cmd.format === 'step' || cmd.format === 'stl' || cmd.format === 'obj') {
|
|
3252
|
+
document.getElementById('btn-reverse-engineer')?.click();
|
|
3253
|
+
}
|
|
3254
|
+
updateStatus(`Import ${cmd.format}`);
|
|
3255
|
+
break;
|
|
3256
|
+
}
|
|
3257
|
+
// --- ASSEMBLY ---
|
|
3258
|
+
case 'assemblyMode': {
|
|
3259
|
+
document.getElementById('tool-assembly')?.click();
|
|
3260
|
+
updateStatus('Assembly mode');
|
|
3261
|
+
break;
|
|
3262
|
+
}
|
|
3263
|
+
case 'explode': {
|
|
3264
|
+
document.getElementById('tool-explode')?.click();
|
|
3265
|
+
updateStatus('Exploded view');
|
|
3266
|
+
break;
|
|
3267
|
+
}
|
|
3268
|
+
// --- MEASURE / SECTION ---
|
|
3269
|
+
case 'measure': {
|
|
3270
|
+
updateStatus('Measure tool — click two points');
|
|
3271
|
+
break;
|
|
3272
|
+
}
|
|
3273
|
+
case 'section': {
|
|
3274
|
+
document.getElementById('btn-section')?.click();
|
|
3275
|
+
updateStatus('Section cut active');
|
|
3276
|
+
break;
|
|
3277
|
+
}
|
|
3278
|
+
// --- SCREENSHOT ---
|
|
3279
|
+
case 'screenshot': {
|
|
3280
|
+
try {
|
|
3281
|
+
const renderer = window._renderer || getScene()?.parent;
|
|
3282
|
+
const canvas = document.querySelector('canvas');
|
|
3283
|
+
if (canvas) {
|
|
3284
|
+
const link = document.createElement('a');
|
|
3285
|
+
link.download = 'cyclecad-screenshot.png';
|
|
3286
|
+
link.href = canvas.toDataURL('image/png');
|
|
3287
|
+
link.click();
|
|
3288
|
+
updateStatus('Screenshot saved');
|
|
3289
|
+
}
|
|
3290
|
+
} catch(e) { updateStatus('Screenshot failed'); }
|
|
3291
|
+
break;
|
|
3292
|
+
}
|
|
3293
|
+
// --- SAVE / LOAD ---
|
|
3294
|
+
case 'save': {
|
|
3295
|
+
try { exportJSON(); updateStatus('Project saved as JSON'); } catch(e) { updateStatus('Save failed'); }
|
|
3296
|
+
break;
|
|
3297
|
+
}
|
|
3298
|
+
case 'load': {
|
|
3299
|
+
updateStatus('Use Import toolbar button to load files');
|
|
3300
|
+
break;
|
|
3301
|
+
}
|
|
3302
|
+
// --- CONSTRAINTS ---
|
|
3303
|
+
case 'addConstraint': {
|
|
3304
|
+
updateStatus(`Constraint: ${cmd.type} — select entities in sketch mode`);
|
|
3305
|
+
break;
|
|
3306
|
+
}
|
|
3307
|
+
default:
|
|
3308
|
+
updateStatus(`Unknown action: ${cmd.action}`);
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
|
|
2879
3312
|
function cancelOperation() {
|
|
2880
3313
|
if (APP.mode === 'sketch') {
|
|
2881
3314
|
endSketch();
|
|
@@ -2896,9 +3329,14 @@
|
|
|
2896
3329
|
|
|
2897
3330
|
function deleteSelected() {
|
|
2898
3331
|
if (!APP.selectedFeature) return;
|
|
2899
|
-
|
|
2900
|
-
|
|
3332
|
+
const idx = APP.features.findIndex(f => f.id === APP.selectedFeature.id);
|
|
3333
|
+
if (APP.selectedFeature.mesh) removeFromScene(APP.selectedFeature.mesh);
|
|
3334
|
+
if (idx >= 0) {
|
|
3335
|
+
APP.features.splice(idx, 1);
|
|
3336
|
+
removeFeature(idx);
|
|
3337
|
+
}
|
|
2901
3338
|
APP.selectedFeature = null;
|
|
3339
|
+
APP.selectedFeatureIndex = -1;
|
|
2902
3340
|
pushHistory();
|
|
2903
3341
|
updateStatus('Feature deleted');
|
|
2904
3342
|
}
|
|
@@ -3598,7 +4036,7 @@
|
|
|
3598
4036
|
'vr-panel': '🥽 CAD2VR',
|
|
3599
4037
|
'generative-panel': '🧬 Generative Design'
|
|
3600
4038
|
};
|
|
3601
|
-
header.innerHTML = `<span style="font-weight:600;font-size:13px;">${titles[panelId] || moduleKey}</span><button onclick="document.getElementById('${panelId}').style.display='none'" style="
|
|
4039
|
+
header.innerHTML = `<span style="font-weight:600;font-size:13px;">${titles[panelId] || moduleKey}</span><button onclick="document.getElementById('${panelId}').style.display='none'" style="width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:rgba(255,80,80,0.15);border:1px solid rgba(255,80,80,0.3);color:#f88;cursor:pointer;font-size:14px;border-radius:5px;font-weight:700;">✕</button>`;
|
|
3602
4040
|
panel.appendChild(header);
|
|
3603
4041
|
|
|
3604
4042
|
const body = document.createElement('div');
|
|
@@ -3951,13 +4389,16 @@
|
|
|
3951
4389
|
if (panel) { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; return; }
|
|
3952
4390
|
panel = document.createElement('div');
|
|
3953
4391
|
panel.id = 'help-tutorials-panel';
|
|
3954
|
-
panel.style.cssText = 'position:fixed;top:60px;left:50%;transform:translateX(-50%);width:680px;max-height:85vh;
|
|
4392
|
+
panel.style.cssText = 'position:fixed;top:60px;left:50%;transform:translateX(-50%);width:680px;max-height:85vh;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:10px;z-index:600;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,0.6);';
|
|
3955
4393
|
panel.innerHTML = `
|
|
3956
4394
|
<div style="display:flex;justify-content:space-between;align-items:center;padding:14px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-tertiary);border-radius:10px 10px 0 0;">
|
|
3957
4395
|
<span style="font-weight:700;font-size:15px;">? Help & Tutorials</span>
|
|
3958
|
-
<button onclick="document.getElementById('help-tutorials-panel').style.display='none'" style="
|
|
4396
|
+
<button onclick="document.getElementById('help-tutorials-panel').style.display='none'" style="width:30px;height:30px;display:flex;align-items:center;justify-content:center;background:rgba(255,80,80,0.15);border:1px solid rgba(255,80,80,0.3);color:#f88;cursor:pointer;font-size:16px;border-radius:6px;font-weight:700;">✕</button>
|
|
4397
|
+
</div>
|
|
4398
|
+
<div style="padding:10px 18px;border-bottom:1px solid var(--border-color);">
|
|
4399
|
+
<input id="help-search-input" type="text" placeholder="Search features, shortcuts, tutorials..." style="width:100%;padding:8px 12px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);font-size:13px;outline:none;box-sizing:border-box;">
|
|
3959
4400
|
</div>
|
|
3960
|
-
<div style="padding:18px;">
|
|
4401
|
+
<div style="padding:18px;overflow-y:auto;flex:1;min-height:0;">
|
|
3961
4402
|
<!-- Tutorials Section -->
|
|
3962
4403
|
<div style="margin-bottom:20px;">
|
|
3963
4404
|
<h3 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--accent-blue);">Tutorials</h3>
|
|
@@ -4041,6 +4482,35 @@
|
|
|
4041
4482
|
</div>
|
|
4042
4483
|
`).join('');
|
|
4043
4484
|
}
|
|
4485
|
+
|
|
4486
|
+
// Wire search
|
|
4487
|
+
const helpSearchInput = document.getElementById('help-search-input');
|
|
4488
|
+
if (helpSearchInput) {
|
|
4489
|
+
let hst;
|
|
4490
|
+
helpSearchInput.addEventListener('input', () => {
|
|
4491
|
+
clearTimeout(hst);
|
|
4492
|
+
hst = setTimeout(() => {
|
|
4493
|
+
const q = helpSearchInput.value.toLowerCase().trim();
|
|
4494
|
+
// Filter feature categories
|
|
4495
|
+
panel.querySelectorAll('#help-feature-list > div').forEach(cat => {
|
|
4496
|
+
const items = cat.querySelectorAll('div:last-child > div');
|
|
4497
|
+
let any = false;
|
|
4498
|
+
items.forEach(item => {
|
|
4499
|
+
const m = !q || item.textContent.toLowerCase().includes(q);
|
|
4500
|
+
item.style.display = m ? '' : 'none';
|
|
4501
|
+
if (m) any = true;
|
|
4502
|
+
});
|
|
4503
|
+
cat.style.display = (!q || any) ? '' : 'none';
|
|
4504
|
+
// Auto-expand matching categories
|
|
4505
|
+
if (q && any) {
|
|
4506
|
+
const content = cat.querySelector('div:last-child');
|
|
4507
|
+
if (content) content.style.display = 'block';
|
|
4508
|
+
}
|
|
4509
|
+
});
|
|
4510
|
+
}, 150);
|
|
4511
|
+
});
|
|
4512
|
+
helpSearchInput.focus();
|
|
4513
|
+
}
|
|
4044
4514
|
});
|
|
4045
4515
|
|
|
4046
4516
|
// ? key opens help
|
|
@@ -4050,6 +4520,86 @@
|
|
|
4050
4520
|
}
|
|
4051
4521
|
});
|
|
4052
4522
|
|
|
4523
|
+
// ========== In-App Console (Ctrl+Shift+C) ==========
|
|
4524
|
+
(function initInAppConsole() {
|
|
4525
|
+
let consolePanel = null;
|
|
4526
|
+
const logs = [];
|
|
4527
|
+
const maxLogs = 200;
|
|
4528
|
+
const origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };
|
|
4529
|
+
|
|
4530
|
+
function capture(level, args) {
|
|
4531
|
+
const ts = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
4532
|
+
const msg = Array.from(args).map(a => {
|
|
4533
|
+
if (typeof a === 'string') return a;
|
|
4534
|
+
try { return JSON.stringify(a); } catch { return String(a); }
|
|
4535
|
+
}).join(' ');
|
|
4536
|
+
logs.push({ ts, level, msg });
|
|
4537
|
+
if (logs.length > maxLogs) logs.shift();
|
|
4538
|
+
if (consolePanel?.style.display !== 'none') renderConsoleLogs();
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4541
|
+
console.log = function() { origConsole.log.apply(console, arguments); capture('log', arguments); };
|
|
4542
|
+
console.warn = function() { origConsole.warn.apply(console, arguments); capture('warn', arguments); };
|
|
4543
|
+
console.error = function() { origConsole.error.apply(console, arguments); capture('error', arguments); };
|
|
4544
|
+
console.info = function() { origConsole.info.apply(console, arguments); capture('info', arguments); };
|
|
4545
|
+
|
|
4546
|
+
window.addEventListener('error', (e) => {
|
|
4547
|
+
capture('error', [`Uncaught: ${e.message} (${e.filename}:${e.lineno})`]);
|
|
4548
|
+
});
|
|
4549
|
+
window.addEventListener('unhandledrejection', (e) => {
|
|
4550
|
+
capture('error', [`Unhandled Promise: ${e.reason}`]);
|
|
4551
|
+
});
|
|
4552
|
+
|
|
4553
|
+
function renderConsoleLogs() {
|
|
4554
|
+
const body = consolePanel?.querySelector('#console-log-body');
|
|
4555
|
+
if (!body) return;
|
|
4556
|
+
body.innerHTML = logs.map(l => {
|
|
4557
|
+
const color = l.level === 'error' ? '#f85149' : l.level === 'warn' ? '#d29922' : l.level === 'info' ? '#58a6ff' : '#8b949e';
|
|
4558
|
+
return `<div style="padding:2px 8px;font-size:11px;font-family:monospace;border-bottom:1px solid rgba(255,255,255,0.05);word-break:break-all;"><span style="color:${color};font-weight:600;margin-right:6px;">[${l.level.toUpperCase()}]</span><span style="color:#555;margin-right:6px;">${l.ts}</span><span style="color:var(--text-primary);">${l.msg.replace(/</g,'<')}</span></div>`;
|
|
4559
|
+
}).join('');
|
|
4560
|
+
body.scrollTop = body.scrollHeight;
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
function toggleConsole() {
|
|
4564
|
+
if (!consolePanel) {
|
|
4565
|
+
consolePanel = document.createElement('div');
|
|
4566
|
+
consolePanel.id = 'in-app-console';
|
|
4567
|
+
consolePanel.style.cssText = 'position:fixed;bottom:32px;left:0;right:0;height:220px;background:#0d1117;border-top:2px solid #f85149;z-index:700;display:flex;flex-direction:column;font-family:monospace;';
|
|
4568
|
+
consolePanel.innerHTML = `
|
|
4569
|
+
<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 12px;background:#161b22;border-bottom:1px solid #30363d;">
|
|
4570
|
+
<div style="display:flex;align-items:center;gap:12px;">
|
|
4571
|
+
<span style="font-weight:700;font-size:12px;color:#f0f0f0;">Console</span>
|
|
4572
|
+
<span id="console-error-count" style="font-size:10px;color:#f85149;"></span>
|
|
4573
|
+
</div>
|
|
4574
|
+
<div style="display:flex;gap:6px;">
|
|
4575
|
+
<button onclick="document.getElementById('in-app-console').querySelector('#console-log-body').innerHTML='';window._consoleLogs=[];" style="padding:3px 10px;background:rgba(255,255,255,0.08);border:1px solid #30363d;color:#8b949e;border-radius:4px;cursor:pointer;font-size:10px;">Clear</button>
|
|
4576
|
+
<button onclick="document.getElementById('in-app-console').style.display='none'" style="width:26px;height:26px;display:flex;align-items:center;justify-content:center;background:rgba(255,80,80,0.15);border:1px solid rgba(255,80,80,0.3);color:#f88;cursor:pointer;font-size:14px;border-radius:4px;font-weight:700;">✕</button>
|
|
4577
|
+
</div>
|
|
4578
|
+
</div>
|
|
4579
|
+
<div id="console-log-body" style="flex:1;overflow-y:auto;min-height:0;"></div>
|
|
4580
|
+
`;
|
|
4581
|
+
document.body.appendChild(consolePanel);
|
|
4582
|
+
} else {
|
|
4583
|
+
consolePanel.style.display = consolePanel.style.display === 'none' ? 'flex' : 'none';
|
|
4584
|
+
}
|
|
4585
|
+
renderConsoleLogs();
|
|
4586
|
+
const errCount = logs.filter(l => l.level === 'error').length;
|
|
4587
|
+
const ec = consolePanel.querySelector('#console-error-count');
|
|
4588
|
+
if (ec) ec.textContent = errCount ? `${errCount} errors` : '';
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4591
|
+
// Ctrl+Shift+C or backtick to open console
|
|
4592
|
+
document.addEventListener('keydown', (e) => {
|
|
4593
|
+
if ((e.ctrlKey && e.shiftKey && e.key === 'C') || (e.key === '`' && !e.ctrlKey && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA')) {
|
|
4594
|
+
e.preventDefault();
|
|
4595
|
+
toggleConsole();
|
|
4596
|
+
}
|
|
4597
|
+
});
|
|
4598
|
+
|
|
4599
|
+
// Expose for toolbar button
|
|
4600
|
+
window._toggleInAppConsole = toggleConsole;
|
|
4601
|
+
})();
|
|
4602
|
+
|
|
4053
4603
|
</script>
|
|
4054
4604
|
|
|
4055
4605
|
<!-- Operation Dialogs -->
|
|
@@ -4399,6 +4949,6 @@
|
|
|
4399
4949
|
</div>
|
|
4400
4950
|
</div>
|
|
4401
4951
|
|
|
4402
|
-
<span id="version-badge" style="position:fixed;bottom:42px;left:50%;transform:translateX(-50%);z-index:999;font-size:0.9rem;color:rgba(255,255,255,0.9);letter-spacing:0.1em;white-space:nowrap;padding:6px 16px;user-select:all;pointer-events:auto;font-family:monospace;font-weight:700;background:rgba(0,0,0,0.7);border:1px solid rgba(88,166,255,0.4);border-radius:6px;text-shadow:0 1px 3px rgba(0,0,0,0.5);" title="cycleCAD version">cycleCAD v0.
|
|
4952
|
+
<span id="version-badge" style="position:fixed;bottom:42px;left:50%;transform:translateX(-50%);z-index:999;font-size:0.9rem;color:rgba(255,255,255,0.9);letter-spacing:0.1em;white-space:nowrap;padding:6px 16px;user-select:all;pointer-events:auto;font-family:monospace;font-weight:700;background:rgba(0,0,0,0.7);border:1px solid rgba(88,166,255,0.4);border-radius:6px;text-shadow:0 1px 3px rgba(0,0,0,0.5);" title="cycleCAD version">cycleCAD v0.9.6</span>
|
|
4403
4953
|
</body>
|
|
4404
4954
|
</html>
|