cyclecad 0.8.7 → 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/app/duo-manifest.json +7375 -0
- package/app/index.html +574 -39
- package/app/js/ai-chat.js +1295 -711
- package/app/js/operations.js +99 -1
- package/app/js/token-dashboard.js +1 -1
- package/app/js/tree.js +14 -1
- 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,8 +1682,19 @@
|
|
|
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>
|
|
1686
1698
|
</div>
|
|
1687
1699
|
<div id="tab-tokens" style="display: none; overflow-y: auto;">
|
|
1688
1700
|
<!-- Token dashboard populated by token-dashboard.js -->
|
|
@@ -1762,25 +1774,27 @@
|
|
|
1762
1774
|
<script type="module">
|
|
1763
1775
|
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
1764
1776
|
const _v = '50';
|
|
1765
|
-
import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, toggleWireframe as vpToggleWireframe, fitToObject
|
|
1766
|
-
|
|
1767
|
-
|
|
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 {
|
|
1782
|
-
import
|
|
1783
|
-
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';
|
|
1784
1798
|
|
|
1785
1799
|
// ========== Application State ==========
|
|
1786
1800
|
const APP = {
|
|
@@ -1994,7 +2008,7 @@
|
|
|
1994
2008
|
|
|
1995
2009
|
// Initialize Agent API — the primary interface
|
|
1996
2010
|
await tryStepAsync('agentAPI', async () => {
|
|
1997
|
-
const agentImports = await import('./js/agent-api.js?v=
|
|
2011
|
+
const agentImports = await import('./js/agent-api.js?v=77');
|
|
1998
2012
|
const agentSession = initAgentAPI({
|
|
1999
2013
|
viewport: {
|
|
2000
2014
|
getCamera,
|
|
@@ -2869,21 +2883,40 @@
|
|
|
2869
2883
|
const splash = document.getElementById('welcome-splash');
|
|
2870
2884
|
if (splash) splash.classList.add('hidden');
|
|
2871
2885
|
|
|
2872
|
-
|
|
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);
|
|
2873
2896
|
const mesh = result.mesh || result;
|
|
2874
2897
|
addToScene(mesh);
|
|
2875
2898
|
if (result.wireframe) addToScene(result.wireframe);
|
|
2876
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
|
+
|
|
2877
2907
|
const feature = {
|
|
2878
2908
|
id: 'feature_' + Date.now(),
|
|
2879
|
-
name: prompt.name ||
|
|
2880
|
-
type:
|
|
2909
|
+
name: prompt.name || desc || 'Part',
|
|
2910
|
+
type: pType,
|
|
2881
2911
|
mesh: mesh,
|
|
2882
|
-
params:
|
|
2912
|
+
params: pParams,
|
|
2913
|
+
_geometryJSON: mesh.geometry ? mesh.geometry.toJSON() : null,
|
|
2914
|
+
_materialColor: mesh.material?.color ? '#' + mesh.material.color.getHexString() : '#58a6ff',
|
|
2883
2915
|
};
|
|
2884
2916
|
APP.features.push(feature);
|
|
2885
2917
|
addFeature(feature);
|
|
2886
2918
|
pushHistory();
|
|
2919
|
+
fitToObject(mesh);
|
|
2887
2920
|
updateStatus(`Created: ${feature.name}`);
|
|
2888
2921
|
} catch (err) {
|
|
2889
2922
|
console.error('AI create failed:', err);
|
|
@@ -2891,6 +2924,391 @@
|
|
|
2891
2924
|
}
|
|
2892
2925
|
}
|
|
2893
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
|
+
|
|
2894
3312
|
function cancelOperation() {
|
|
2895
3313
|
if (APP.mode === 'sketch') {
|
|
2896
3314
|
endSketch();
|
|
@@ -2911,9 +3329,14 @@
|
|
|
2911
3329
|
|
|
2912
3330
|
function deleteSelected() {
|
|
2913
3331
|
if (!APP.selectedFeature) return;
|
|
2914
|
-
|
|
2915
|
-
|
|
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
|
+
}
|
|
2916
3338
|
APP.selectedFeature = null;
|
|
3339
|
+
APP.selectedFeatureIndex = -1;
|
|
2917
3340
|
pushHistory();
|
|
2918
3341
|
updateStatus('Feature deleted');
|
|
2919
3342
|
}
|
|
@@ -3613,7 +4036,7 @@
|
|
|
3613
4036
|
'vr-panel': '🥽 CAD2VR',
|
|
3614
4037
|
'generative-panel': '🧬 Generative Design'
|
|
3615
4038
|
};
|
|
3616
|
-
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>`;
|
|
3617
4040
|
panel.appendChild(header);
|
|
3618
4041
|
|
|
3619
4042
|
const body = document.createElement('div');
|
|
@@ -3966,13 +4389,16 @@
|
|
|
3966
4389
|
if (panel) { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; return; }
|
|
3967
4390
|
panel = document.createElement('div');
|
|
3968
4391
|
panel.id = 'help-tutorials-panel';
|
|
3969
|
-
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);';
|
|
3970
4393
|
panel.innerHTML = `
|
|
3971
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;">
|
|
3972
4395
|
<span style="font-weight:700;font-size:15px;">? Help & Tutorials</span>
|
|
3973
|
-
<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;">
|
|
3974
4400
|
</div>
|
|
3975
|
-
<div style="padding:18px;">
|
|
4401
|
+
<div style="padding:18px;overflow-y:auto;flex:1;min-height:0;">
|
|
3976
4402
|
<!-- Tutorials Section -->
|
|
3977
4403
|
<div style="margin-bottom:20px;">
|
|
3978
4404
|
<h3 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--accent-blue);">Tutorials</h3>
|
|
@@ -4056,6 +4482,35 @@
|
|
|
4056
4482
|
</div>
|
|
4057
4483
|
`).join('');
|
|
4058
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
|
+
}
|
|
4059
4514
|
});
|
|
4060
4515
|
|
|
4061
4516
|
// ? key opens help
|
|
@@ -4065,6 +4520,86 @@
|
|
|
4065
4520
|
}
|
|
4066
4521
|
});
|
|
4067
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
|
+
|
|
4068
4603
|
</script>
|
|
4069
4604
|
|
|
4070
4605
|
<!-- Operation Dialogs -->
|
|
@@ -4414,6 +4949,6 @@
|
|
|
4414
4949
|
</div>
|
|
4415
4950
|
</div>
|
|
4416
4951
|
|
|
4417
|
-
<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>
|
|
4418
4953
|
</body>
|
|
4419
4954
|
</html>
|