cyclecad 0.8.7 → 0.9.7
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 +581 -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/app/test-agent-v2.html +1494 -0
- package/app/tutorials/explodeview-integration.html +2178 -0
- package/package.json +1 -1
- package/test-report.html +156 -0
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,26 @@
|
|
|
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; min-height: 0;">
|
|
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 style="margin-bottom:10px;"><b>Agent API</b><br>window.cycleCAD.execute({ method: "shape.cylinder", params: { radius: 25, height: 60 } })</div>
|
|
1698
|
+
<hr style="border-color:var(--border-color);margin:16px 0;">
|
|
1699
|
+
<h3 style="margin:0 0 12px 0;color:var(--accent-green);font-size:15px;">ExplodeView Integration</h3>
|
|
1700
|
+
<div style="margin-bottom:10px;"><b>Viewer Mode</b><br>Press <b>V</b> or click Viewer Mode to switch to ExplodeView. All 57 viewer features are available inside cycleCAD — shared 3D scene, no duplication.</div>
|
|
1701
|
+
<div style="margin-bottom:10px;"><b>Available in Viewer Mode</b><br>Assembly Tree, Explode/Collapse, Section Cut, Part Selection, BOM Export, Annotations, Measurement, AI Part ID, Heatmaps, Service Mode, and 45+ more tools.</div>
|
|
1702
|
+
<div style="margin-bottom:10px;"><b>Viewer API</b><br>viewer.toggleViewerMode(), viewer.selectPart(idx), viewer.explodeParts(0.5), viewer.setSectionCut('x', 100), viewer.exportBOM()</div>
|
|
1703
|
+
<div style="margin-bottom:10px;"><b>Docker (all services)</b><br>docker-compose up -d<br>ExplodeView :8080 · cycleCAD :3000 · STEP Converter :8787</div>
|
|
1704
|
+
<div style="padding:8px 10px;background:rgba(88,166,255,0.08);border:1px solid rgba(88,166,255,0.2);border-radius:6px;font-size:12px;color:var(--accent-blue);">Full integration tutorial: <a href="tutorials/explodeview-integration.html" style="color:var(--accent-blue);">tutorials/explodeview-integration.html</a></div>
|
|
1686
1705
|
</div>
|
|
1687
1706
|
<div id="tab-tokens" style="display: none; overflow-y: auto;">
|
|
1688
1707
|
<!-- Token dashboard populated by token-dashboard.js -->
|
|
@@ -1762,25 +1781,27 @@
|
|
|
1762
1781
|
<script type="module">
|
|
1763
1782
|
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
1764
1783
|
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 {
|
|
1784
|
+
import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, toggleWireframe as vpToggleWireframe, fitToObject } from './js/viewport.js?v=77';
|
|
1785
|
+
// fitAll defined locally to avoid import failures from cached viewport.js
|
|
1786
|
+
function fitAll(padding = 1.2) { const s = getScene(); if (s) fitToObject(s, padding); }
|
|
1787
|
+
import { startSketch, endSketch, setTool, getEntities, clearSketch } from './js/sketch.js?v=77';
|
|
1788
|
+
import { extrudeProfile, createPrimitive, rebuildFeature, createMaterial } from './js/operations.js?v=77';
|
|
1789
|
+
import { initChat, parseCADPrompt, addMessage } from './js/ai-chat.js?v=77';
|
|
1790
|
+
import { initTree, addFeature, selectFeature, onSelect, removeFeature } from './js/tree.js?v=77';
|
|
1791
|
+
import { initParams, showParams, onParamChange } from './js/params.js?v=77';
|
|
1792
|
+
import { exportSTL, exportOBJ, exportJSON } from './js/export.js?v=77';
|
|
1793
|
+
import { initShortcuts } from './js/shortcuts.js?v=77';
|
|
1794
|
+
import { createReverseEngineerPanel, importFile, analyzeGeometry, reconstructFeatureTree, createWalkthrough } from './js/reverse-engineer.js?v=77';
|
|
1795
|
+
import { createInventorPanel, parseInventorFile } from './js/inventor-parser.js?v=77';
|
|
1796
|
+
import { loadProject, showFolderPicker, parseIPJ } from './js/project-loader.js?v=77';
|
|
1797
|
+
import { initProjectBrowser, showBrowser, hideBrowser, setProject, onFileSelect } from './js/project-browser.js?v=77';
|
|
1798
|
+
import { generateGuide, renderGuide, exportGuideHTML } from './js/rebuild-guide.js?v=77';
|
|
1799
|
+
import { solveConstraints, addConstraint, removeConstraint, autoDetectConstraints, isFullyConstrained, getAllConstraints, clearAllConstraints } from './js/constraint-solver.js?v=77';
|
|
1800
|
+
import { createSweep, createLoft, createBend, createFlange, createTab, createSlot, unfoldSheetMetal, createSpring, createThread } from './js/advanced-ops.js?v=77';
|
|
1801
|
+
import Assembly from './js/assembly.js?v=77';
|
|
1802
|
+
import { exportSketchToDXF, exportProjectionToDXF, exportMultiViewDXF, export3DDXF, downloadDXF } from './js/dxf-export.js?v=77';
|
|
1803
|
+
import { initAgentAPI } from './js/agent-api.js?v=77';
|
|
1804
|
+
import { initTokenDashboard } from './js/token-dashboard.js?v=77';
|
|
1784
1805
|
|
|
1785
1806
|
// ========== Application State ==========
|
|
1786
1807
|
const APP = {
|
|
@@ -1994,7 +2015,7 @@
|
|
|
1994
2015
|
|
|
1995
2016
|
// Initialize Agent API — the primary interface
|
|
1996
2017
|
await tryStepAsync('agentAPI', async () => {
|
|
1997
|
-
const agentImports = await import('./js/agent-api.js?v=
|
|
2018
|
+
const agentImports = await import('./js/agent-api.js?v=77');
|
|
1998
2019
|
const agentSession = initAgentAPI({
|
|
1999
2020
|
viewport: {
|
|
2000
2021
|
getCamera,
|
|
@@ -2869,21 +2890,40 @@
|
|
|
2869
2890
|
const splash = document.getElementById('welcome-splash');
|
|
2870
2891
|
if (splash) splash.classList.add('hidden');
|
|
2871
2892
|
|
|
2872
|
-
|
|
2893
|
+
// ---- Handle ACTION commands (delete, undo, move, etc.) ----
|
|
2894
|
+
if (prompt.action) {
|
|
2895
|
+
executeAction(prompt);
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// ---- Handle CREATE commands (box, cylinder, etc.) ----
|
|
2900
|
+
const pType = prompt.type || prompt.action;
|
|
2901
|
+
const pParams = prompt.params || prompt;
|
|
2902
|
+
const result = createPrimitive(pType, pParams);
|
|
2873
2903
|
const mesh = result.mesh || result;
|
|
2874
2904
|
addToScene(mesh);
|
|
2875
2905
|
if (result.wireframe) addToScene(result.wireframe);
|
|
2876
2906
|
|
|
2907
|
+
// Build descriptive name
|
|
2908
|
+
let desc = pType;
|
|
2909
|
+
if (pParams.width && pParams.height) desc = `${pType} ${pParams.width}×${pParams.height}${pParams.thickness ? '×' + pParams.thickness : ''}mm`;
|
|
2910
|
+
else if (pParams.radius && pParams.height) desc = `${pType} r${pParams.radius} h${pParams.height}mm`;
|
|
2911
|
+
else if (pParams.radius) desc = `${pType} r${pParams.radius}mm`;
|
|
2912
|
+
else if (pParams.width) desc = `${pType} ${pParams.width}mm`;
|
|
2913
|
+
|
|
2877
2914
|
const feature = {
|
|
2878
2915
|
id: 'feature_' + Date.now(),
|
|
2879
|
-
name: prompt.name ||
|
|
2880
|
-
type:
|
|
2916
|
+
name: prompt.name || desc || 'Part',
|
|
2917
|
+
type: pType,
|
|
2881
2918
|
mesh: mesh,
|
|
2882
|
-
params:
|
|
2919
|
+
params: pParams,
|
|
2920
|
+
_geometryJSON: mesh.geometry ? mesh.geometry.toJSON() : null,
|
|
2921
|
+
_materialColor: mesh.material?.color ? '#' + mesh.material.color.getHexString() : '#58a6ff',
|
|
2883
2922
|
};
|
|
2884
2923
|
APP.features.push(feature);
|
|
2885
2924
|
addFeature(feature);
|
|
2886
2925
|
pushHistory();
|
|
2926
|
+
fitToObject(mesh);
|
|
2887
2927
|
updateStatus(`Created: ${feature.name}`);
|
|
2888
2928
|
} catch (err) {
|
|
2889
2929
|
console.error('AI create failed:', err);
|
|
@@ -2891,6 +2931,391 @@
|
|
|
2891
2931
|
}
|
|
2892
2932
|
}
|
|
2893
2933
|
|
|
2934
|
+
function executeAction(cmd) {
|
|
2935
|
+
const features = APP.features;
|
|
2936
|
+
|
|
2937
|
+
switch (cmd.action) {
|
|
2938
|
+
case 'delete': {
|
|
2939
|
+
let idx = cmd.index;
|
|
2940
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
2941
|
+
if (idx >= 0 && idx < features.length) {
|
|
2942
|
+
const f = features[idx];
|
|
2943
|
+
if (f.mesh) removeFromScene(f.mesh);
|
|
2944
|
+
features.splice(idx, 1);
|
|
2945
|
+
removeFeature(idx);
|
|
2946
|
+
APP.selectedFeature = null;
|
|
2947
|
+
pushHistory();
|
|
2948
|
+
updateStatus(`Deleted: ${f.name || 'Part'}`);
|
|
2949
|
+
}
|
|
2950
|
+
break;
|
|
2951
|
+
}
|
|
2952
|
+
case 'undo': undo(); break;
|
|
2953
|
+
case 'redo': redo(); break;
|
|
2954
|
+
case 'clearScene': {
|
|
2955
|
+
features.forEach(f => { if (f.mesh) removeFromScene(f.mesh); });
|
|
2956
|
+
APP.features = [];
|
|
2957
|
+
// Clear tree
|
|
2958
|
+
const treeEl = document.getElementById('feature-tree');
|
|
2959
|
+
if (treeEl) treeEl.innerHTML = '';
|
|
2960
|
+
pushHistory();
|
|
2961
|
+
updateStatus('Scene cleared');
|
|
2962
|
+
break;
|
|
2963
|
+
}
|
|
2964
|
+
case 'hide': {
|
|
2965
|
+
let idx = cmd.index;
|
|
2966
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
2967
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
2968
|
+
features[idx].mesh.visible = false;
|
|
2969
|
+
updateStatus(`Hidden: ${features[idx].name || 'Part'}`);
|
|
2970
|
+
}
|
|
2971
|
+
break;
|
|
2972
|
+
}
|
|
2973
|
+
case 'showAll': {
|
|
2974
|
+
features.forEach(f => { if (f.mesh) f.mesh.visible = true; });
|
|
2975
|
+
updateStatus('All parts visible');
|
|
2976
|
+
break;
|
|
2977
|
+
}
|
|
2978
|
+
case 'select': {
|
|
2979
|
+
let idx = cmd.index;
|
|
2980
|
+
if (idx >= 0 && idx < features.length) {
|
|
2981
|
+
APP.selectedFeature = features[idx];
|
|
2982
|
+
APP.selectedFeatureIndex = idx;
|
|
2983
|
+
selectFeature(idx);
|
|
2984
|
+
updateStatus(`Selected: ${features[idx].name || 'Part'}`);
|
|
2985
|
+
}
|
|
2986
|
+
break;
|
|
2987
|
+
}
|
|
2988
|
+
case 'move': {
|
|
2989
|
+
let idx = cmd.index;
|
|
2990
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
2991
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
2992
|
+
const mesh = features[idx].mesh;
|
|
2993
|
+
const dist = cmd.distance || 20;
|
|
2994
|
+
if (cmd.axis === 'x') mesh.position.x += dist;
|
|
2995
|
+
else if (cmd.axis === 'y') mesh.position.y += dist;
|
|
2996
|
+
else if (cmd.axis === 'z') mesh.position.z += dist;
|
|
2997
|
+
pushHistory();
|
|
2998
|
+
updateStatus(`Moved: ${features[idx].name || 'Part'}`);
|
|
2999
|
+
}
|
|
3000
|
+
break;
|
|
3001
|
+
}
|
|
3002
|
+
case 'rotate': {
|
|
3003
|
+
let idx = cmd.index;
|
|
3004
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3005
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
3006
|
+
const mesh = features[idx].mesh;
|
|
3007
|
+
const angle = (cmd.angle || 90) * Math.PI / 180;
|
|
3008
|
+
if (cmd.axis === 'x') mesh.rotation.x += angle;
|
|
3009
|
+
else if (cmd.axis === 'y') mesh.rotation.y += angle;
|
|
3010
|
+
else if (cmd.axis === 'z') mesh.rotation.z += angle;
|
|
3011
|
+
pushHistory();
|
|
3012
|
+
updateStatus(`Rotated: ${features[idx].name || 'Part'}`);
|
|
3013
|
+
}
|
|
3014
|
+
break;
|
|
3015
|
+
}
|
|
3016
|
+
case 'scale': {
|
|
3017
|
+
let idx = cmd.index;
|
|
3018
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3019
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
3020
|
+
const mesh = features[idx].mesh;
|
|
3021
|
+
const factor = cmd.factor || 1.5;
|
|
3022
|
+
mesh.scale.multiplyScalar(factor);
|
|
3023
|
+
pushHistory();
|
|
3024
|
+
updateStatus(`Scaled: ${features[idx].name || 'Part'} × ${factor}`);
|
|
3025
|
+
}
|
|
3026
|
+
break;
|
|
3027
|
+
}
|
|
3028
|
+
case 'duplicate': {
|
|
3029
|
+
let idx = cmd.index;
|
|
3030
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3031
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh) {
|
|
3032
|
+
const orig = features[idx];
|
|
3033
|
+
const clonedMesh = orig.mesh.clone();
|
|
3034
|
+
clonedMesh.position.x += 30; // offset so it's visible
|
|
3035
|
+
addToScene(clonedMesh);
|
|
3036
|
+
const newFeature = {
|
|
3037
|
+
id: 'feature_' + Date.now(),
|
|
3038
|
+
name: (orig.name || 'Part') + ' (copy)',
|
|
3039
|
+
type: orig.type,
|
|
3040
|
+
mesh: clonedMesh,
|
|
3041
|
+
params: { ...(orig.params || {}) },
|
|
3042
|
+
_geometryJSON: clonedMesh.geometry ? clonedMesh.geometry.toJSON() : null,
|
|
3043
|
+
_materialColor: orig._materialColor || '#58a6ff',
|
|
3044
|
+
};
|
|
3045
|
+
APP.features.push(newFeature);
|
|
3046
|
+
addFeature(newFeature);
|
|
3047
|
+
pushHistory();
|
|
3048
|
+
updateStatus(`Duplicated: ${orig.name || 'Part'}`);
|
|
3049
|
+
}
|
|
3050
|
+
break;
|
|
3051
|
+
}
|
|
3052
|
+
case 'color': {
|
|
3053
|
+
let idx = cmd.index;
|
|
3054
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3055
|
+
if (idx >= 0 && idx < features.length && features[idx].mesh?.material) {
|
|
3056
|
+
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 };
|
|
3057
|
+
const hex = colorMap[cmd.color] || 0x58a6ff;
|
|
3058
|
+
features[idx].mesh.material.color.setHex(hex);
|
|
3059
|
+
features[idx]._materialColor = '#' + hex.toString(16).padStart(6, '0');
|
|
3060
|
+
updateStatus(`Colored: ${features[idx].name || 'Part'} → ${cmd.color}`);
|
|
3061
|
+
}
|
|
3062
|
+
break;
|
|
3063
|
+
}
|
|
3064
|
+
case 'rename': {
|
|
3065
|
+
let idx = cmd.index;
|
|
3066
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3067
|
+
if (idx >= 0 && idx < features.length) {
|
|
3068
|
+
features[idx].name = cmd.name;
|
|
3069
|
+
updateStatus(`Renamed to: ${cmd.name}`);
|
|
3070
|
+
}
|
|
3071
|
+
break;
|
|
3072
|
+
}
|
|
3073
|
+
case 'fitAll': {
|
|
3074
|
+
fitAll();
|
|
3075
|
+
updateStatus('View reset');
|
|
3076
|
+
break;
|
|
3077
|
+
}
|
|
3078
|
+
case 'wireframe': {
|
|
3079
|
+
vpToggleWireframe();
|
|
3080
|
+
updateStatus('Wireframe toggled');
|
|
3081
|
+
break;
|
|
3082
|
+
}
|
|
3083
|
+
case 'grid': {
|
|
3084
|
+
vpToggleGrid();
|
|
3085
|
+
updateStatus('Grid toggled');
|
|
3086
|
+
break;
|
|
3087
|
+
}
|
|
3088
|
+
case 'export': {
|
|
3089
|
+
if (cmd.format === 'stl') exportSTL();
|
|
3090
|
+
else if (cmd.format === 'obj') exportOBJ();
|
|
3091
|
+
else exportSTL();
|
|
3092
|
+
updateStatus(`Exported ${(cmd.format || 'STL').toUpperCase()}`);
|
|
3093
|
+
break;
|
|
3094
|
+
}
|
|
3095
|
+
case 'booleanSubtract':
|
|
3096
|
+
case 'booleanIntersect':
|
|
3097
|
+
case 'booleanUnion': {
|
|
3098
|
+
updateStatus(`Boolean ${cmd.action.replace('boolean', '')} — coming soon (requires CSG kernel)`);
|
|
3099
|
+
break;
|
|
3100
|
+
}
|
|
3101
|
+
// --- MODIFY DIMENSION (recreate part with new params) ---
|
|
3102
|
+
case 'modifyDimension': {
|
|
3103
|
+
let idx = cmd.index;
|
|
3104
|
+
if (idx === -1 || idx === undefined) idx = features.length - 1;
|
|
3105
|
+
if (idx >= 0 && idx < features.length) {
|
|
3106
|
+
const f = features[idx];
|
|
3107
|
+
const oldParams = { ...(f.params || {}) };
|
|
3108
|
+
const dim = cmd.dimension;
|
|
3109
|
+
const val = cmd.value;
|
|
3110
|
+
|
|
3111
|
+
// Map dimension name to param key
|
|
3112
|
+
if (dim === 'height' || dim === 'tall' || dim === 'high') {
|
|
3113
|
+
oldParams.height = val;
|
|
3114
|
+
} else if (dim === 'width' || dim === 'wide') {
|
|
3115
|
+
oldParams.width = val;
|
|
3116
|
+
} else if (dim === 'depth' || dim === 'deep' || dim === 'long') {
|
|
3117
|
+
oldParams.depth = val;
|
|
3118
|
+
} else if (dim === 'radius') {
|
|
3119
|
+
oldParams.radius = val;
|
|
3120
|
+
} else if (dim === 'diameter') {
|
|
3121
|
+
oldParams.radius = val / 2;
|
|
3122
|
+
} else if (dim === 'thickness') {
|
|
3123
|
+
oldParams.thickness = val;
|
|
3124
|
+
} else if (dim === 'size') {
|
|
3125
|
+
oldParams.width = val; oldParams.height = val; oldParams.depth = val;
|
|
3126
|
+
if (oldParams.radius !== undefined) oldParams.radius = val / 2;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// Recreate the part with new params
|
|
3130
|
+
try {
|
|
3131
|
+
const pType = f.type || 'box';
|
|
3132
|
+
const result = createPrimitive(pType, oldParams);
|
|
3133
|
+
const newMesh = result.mesh || result;
|
|
3134
|
+
|
|
3135
|
+
// Copy position/rotation from old mesh
|
|
3136
|
+
if (f.mesh) {
|
|
3137
|
+
newMesh.position.copy(f.mesh.position);
|
|
3138
|
+
newMesh.rotation.copy(f.mesh.rotation);
|
|
3139
|
+
newMesh.scale.copy(f.mesh.scale);
|
|
3140
|
+
removeFromScene(f.mesh);
|
|
3141
|
+
}
|
|
3142
|
+
addToScene(newMesh);
|
|
3143
|
+
if (result.wireframe) addToScene(result.wireframe);
|
|
3144
|
+
|
|
3145
|
+
// Update feature in place
|
|
3146
|
+
f.mesh = newMesh;
|
|
3147
|
+
f.params = oldParams;
|
|
3148
|
+
f._geometryJSON = newMesh.geometry ? newMesh.geometry.toJSON() : null;
|
|
3149
|
+
f._materialColor = newMesh.material?.color ? '#' + newMesh.material.color.getHexString() : f._materialColor;
|
|
3150
|
+
|
|
3151
|
+
// Update name
|
|
3152
|
+
let desc = pType;
|
|
3153
|
+
if (oldParams.width && oldParams.height) desc = `${pType} ${oldParams.width}×${oldParams.height}${oldParams.depth ? '×' + oldParams.depth : ''}mm`;
|
|
3154
|
+
else if (oldParams.radius && oldParams.height) desc = `${pType} r${oldParams.radius} h${oldParams.height}mm`;
|
|
3155
|
+
else if (oldParams.radius) desc = `${pType} r${oldParams.radius}mm`;
|
|
3156
|
+
f.name = desc;
|
|
3157
|
+
|
|
3158
|
+
pushHistory();
|
|
3159
|
+
fitToObject(newMesh);
|
|
3160
|
+
updateStatus(`Modified: ${f.name}`);
|
|
3161
|
+
} catch (err) {
|
|
3162
|
+
console.error('Modify failed:', err);
|
|
3163
|
+
updateStatus('Modify failed: ' + err.message);
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
break;
|
|
3167
|
+
}
|
|
3168
|
+
// --- SKETCH ---
|
|
3169
|
+
case 'startSketch': {
|
|
3170
|
+
startNewSketch();
|
|
3171
|
+
updateStatus('Sketch mode active');
|
|
3172
|
+
break;
|
|
3173
|
+
}
|
|
3174
|
+
case 'endSketch': {
|
|
3175
|
+
endSketch();
|
|
3176
|
+
APP.mode = 'idle';
|
|
3177
|
+
updateStatus('Sketch completed');
|
|
3178
|
+
break;
|
|
3179
|
+
}
|
|
3180
|
+
case 'sketchTool': {
|
|
3181
|
+
if (APP.mode !== 'sketch') startNewSketch();
|
|
3182
|
+
if (cmd.tool) setTool(cmd.tool);
|
|
3183
|
+
updateStatus(`Sketch tool: ${cmd.tool}`);
|
|
3184
|
+
break;
|
|
3185
|
+
}
|
|
3186
|
+
// --- EXTRUDE / REVOLVE ---
|
|
3187
|
+
case 'extrude': {
|
|
3188
|
+
doExtrude();
|
|
3189
|
+
break;
|
|
3190
|
+
}
|
|
3191
|
+
case 'revolve': {
|
|
3192
|
+
updateStatus('Revolve — select sketch first');
|
|
3193
|
+
break;
|
|
3194
|
+
}
|
|
3195
|
+
case 'cut': {
|
|
3196
|
+
updateStatus('Cut mode — select tool body');
|
|
3197
|
+
break;
|
|
3198
|
+
}
|
|
3199
|
+
// --- ADVANCED OPS ---
|
|
3200
|
+
case 'sweep': { updateStatus('Sweep — select profile and path'); break; }
|
|
3201
|
+
case 'loft': { updateStatus('Loft — select profiles'); break; }
|
|
3202
|
+
case 'shell': { updateStatus(`Shell ${cmd.thickness || 2}mm — select face to remove`); break; }
|
|
3203
|
+
case 'pattern': { updateStatus(`Pattern ${cmd.count || 4} copies`); break; }
|
|
3204
|
+
case 'mirror': { updateStatus(`Mirror across ${(cmd.plane || 'Y').toUpperCase()} plane`); break; }
|
|
3205
|
+
case 'thread': { updateStatus('Thread — select cylindrical face'); break; }
|
|
3206
|
+
case 'spring': { updateStatus(`Spring d=${cmd.diameter || 20}mm`); break; }
|
|
3207
|
+
case 'bend': { updateStatus('Sheet metal bend'); break; }
|
|
3208
|
+
case 'unfold': { updateStatus('Unfolding flat pattern'); break; }
|
|
3209
|
+
// --- VIEWS ---
|
|
3210
|
+
case 'setView': {
|
|
3211
|
+
const viewMap = { front: 'front', top: 'top', right: 'right', left: 'left', back: 'back', bottom: 'bottom', isometric: 'iso', iso: 'iso' };
|
|
3212
|
+
const v = viewMap[cmd.view] || 'front';
|
|
3213
|
+
try { setView(v); } catch(e) {}
|
|
3214
|
+
updateStatus(`${cmd.view} view`);
|
|
3215
|
+
break;
|
|
3216
|
+
}
|
|
3217
|
+
case 'zoomIn': {
|
|
3218
|
+
const cam = getCamera();
|
|
3219
|
+
if (cam) { cam.position.multiplyScalar(0.75); cam.updateProjectionMatrix(); }
|
|
3220
|
+
updateStatus('Zoomed in');
|
|
3221
|
+
break;
|
|
3222
|
+
}
|
|
3223
|
+
case 'zoomOut': {
|
|
3224
|
+
const cam2 = getCamera();
|
|
3225
|
+
if (cam2) { cam2.position.multiplyScalar(1.33); cam2.updateProjectionMatrix(); }
|
|
3226
|
+
updateStatus('Zoomed out');
|
|
3227
|
+
break;
|
|
3228
|
+
}
|
|
3229
|
+
case 'toggleTheme': {
|
|
3230
|
+
document.getElementById('btn-theme-toggle')?.click();
|
|
3231
|
+
updateStatus('Theme toggled');
|
|
3232
|
+
break;
|
|
3233
|
+
}
|
|
3234
|
+
// --- PANELS ---
|
|
3235
|
+
case 'openPanel': {
|
|
3236
|
+
const panelBtnMap = {
|
|
3237
|
+
help: 'btn-help', properties: null, guide: null, tokens: null,
|
|
3238
|
+
marketplace: 'btn-marketplace-v2', gdt: 'btn-gdt', misumi: 'btn-misumi',
|
|
3239
|
+
console: 'btn-console', dfm: 'btn-dfm', copilot: 'btn-ai-copilot',
|
|
3240
|
+
reverseEngineer: 'btn-reverse-engineer', materials: 'btn-materials',
|
|
3241
|
+
generative: 'btn-generative', cam: 'btn-cam', gcode: 'btn-gcode',
|
|
3242
|
+
collab: 'btn-collab', vr: 'btn-vr'
|
|
3243
|
+
};
|
|
3244
|
+
// For tab panels, switch tabs
|
|
3245
|
+
if (['properties', 'guide', 'tokens'].includes(cmd.panel)) {
|
|
3246
|
+
document.querySelector(`[data-tab="${cmd.panel}"]`)?.click();
|
|
3247
|
+
} else {
|
|
3248
|
+
const btnId = panelBtnMap[cmd.panel];
|
|
3249
|
+
if (btnId) document.getElementById(btnId)?.click();
|
|
3250
|
+
}
|
|
3251
|
+
updateStatus(`Opened: ${cmd.panel}`);
|
|
3252
|
+
break;
|
|
3253
|
+
}
|
|
3254
|
+
// --- IMPORT ---
|
|
3255
|
+
case 'import': {
|
|
3256
|
+
if (cmd.format === 'inventor') {
|
|
3257
|
+
document.getElementById('btn-inventor-import')?.click();
|
|
3258
|
+
} else if (cmd.format === 'step' || cmd.format === 'stl' || cmd.format === 'obj') {
|
|
3259
|
+
document.getElementById('btn-reverse-engineer')?.click();
|
|
3260
|
+
}
|
|
3261
|
+
updateStatus(`Import ${cmd.format}`);
|
|
3262
|
+
break;
|
|
3263
|
+
}
|
|
3264
|
+
// --- ASSEMBLY ---
|
|
3265
|
+
case 'assemblyMode': {
|
|
3266
|
+
document.getElementById('tool-assembly')?.click();
|
|
3267
|
+
updateStatus('Assembly mode');
|
|
3268
|
+
break;
|
|
3269
|
+
}
|
|
3270
|
+
case 'explode': {
|
|
3271
|
+
document.getElementById('tool-explode')?.click();
|
|
3272
|
+
updateStatus('Exploded view');
|
|
3273
|
+
break;
|
|
3274
|
+
}
|
|
3275
|
+
// --- MEASURE / SECTION ---
|
|
3276
|
+
case 'measure': {
|
|
3277
|
+
updateStatus('Measure tool — click two points');
|
|
3278
|
+
break;
|
|
3279
|
+
}
|
|
3280
|
+
case 'section': {
|
|
3281
|
+
document.getElementById('btn-section')?.click();
|
|
3282
|
+
updateStatus('Section cut active');
|
|
3283
|
+
break;
|
|
3284
|
+
}
|
|
3285
|
+
// --- SCREENSHOT ---
|
|
3286
|
+
case 'screenshot': {
|
|
3287
|
+
try {
|
|
3288
|
+
const renderer = window._renderer || getScene()?.parent;
|
|
3289
|
+
const canvas = document.querySelector('canvas');
|
|
3290
|
+
if (canvas) {
|
|
3291
|
+
const link = document.createElement('a');
|
|
3292
|
+
link.download = 'cyclecad-screenshot.png';
|
|
3293
|
+
link.href = canvas.toDataURL('image/png');
|
|
3294
|
+
link.click();
|
|
3295
|
+
updateStatus('Screenshot saved');
|
|
3296
|
+
}
|
|
3297
|
+
} catch(e) { updateStatus('Screenshot failed'); }
|
|
3298
|
+
break;
|
|
3299
|
+
}
|
|
3300
|
+
// --- SAVE / LOAD ---
|
|
3301
|
+
case 'save': {
|
|
3302
|
+
try { exportJSON(); updateStatus('Project saved as JSON'); } catch(e) { updateStatus('Save failed'); }
|
|
3303
|
+
break;
|
|
3304
|
+
}
|
|
3305
|
+
case 'load': {
|
|
3306
|
+
updateStatus('Use Import toolbar button to load files');
|
|
3307
|
+
break;
|
|
3308
|
+
}
|
|
3309
|
+
// --- CONSTRAINTS ---
|
|
3310
|
+
case 'addConstraint': {
|
|
3311
|
+
updateStatus(`Constraint: ${cmd.type} — select entities in sketch mode`);
|
|
3312
|
+
break;
|
|
3313
|
+
}
|
|
3314
|
+
default:
|
|
3315
|
+
updateStatus(`Unknown action: ${cmd.action}`);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
|
|
2894
3319
|
function cancelOperation() {
|
|
2895
3320
|
if (APP.mode === 'sketch') {
|
|
2896
3321
|
endSketch();
|
|
@@ -2911,9 +3336,14 @@
|
|
|
2911
3336
|
|
|
2912
3337
|
function deleteSelected() {
|
|
2913
3338
|
if (!APP.selectedFeature) return;
|
|
2914
|
-
|
|
2915
|
-
|
|
3339
|
+
const idx = APP.features.findIndex(f => f.id === APP.selectedFeature.id);
|
|
3340
|
+
if (APP.selectedFeature.mesh) removeFromScene(APP.selectedFeature.mesh);
|
|
3341
|
+
if (idx >= 0) {
|
|
3342
|
+
APP.features.splice(idx, 1);
|
|
3343
|
+
removeFeature(idx);
|
|
3344
|
+
}
|
|
2916
3345
|
APP.selectedFeature = null;
|
|
3346
|
+
APP.selectedFeatureIndex = -1;
|
|
2917
3347
|
pushHistory();
|
|
2918
3348
|
updateStatus('Feature deleted');
|
|
2919
3349
|
}
|
|
@@ -3613,7 +4043,7 @@
|
|
|
3613
4043
|
'vr-panel': '🥽 CAD2VR',
|
|
3614
4044
|
'generative-panel': '🧬 Generative Design'
|
|
3615
4045
|
};
|
|
3616
|
-
header.innerHTML = `<span style="font-weight:600;font-size:13px;">${titles[panelId] || moduleKey}</span><button onclick="document.getElementById('${panelId}').style.display='none'" style="
|
|
4046
|
+
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
4047
|
panel.appendChild(header);
|
|
3618
4048
|
|
|
3619
4049
|
const body = document.createElement('div');
|
|
@@ -3966,13 +4396,16 @@
|
|
|
3966
4396
|
if (panel) { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; return; }
|
|
3967
4397
|
panel = document.createElement('div');
|
|
3968
4398
|
panel.id = 'help-tutorials-panel';
|
|
3969
|
-
panel.style.cssText = 'position:fixed;top:60px;left:50%;transform:translateX(-50%);width:680px;max-height:85vh;
|
|
4399
|
+
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
4400
|
panel.innerHTML = `
|
|
3971
4401
|
<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
4402
|
<span style="font-weight:700;font-size:15px;">? Help & Tutorials</span>
|
|
3973
|
-
<button onclick="document.getElementById('help-tutorials-panel').style.display='none'" style="
|
|
4403
|
+
<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>
|
|
4404
|
+
</div>
|
|
4405
|
+
<div style="padding:10px 18px;border-bottom:1px solid var(--border-color);">
|
|
4406
|
+
<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
4407
|
</div>
|
|
3975
|
-
<div style="padding:18px;">
|
|
4408
|
+
<div style="padding:18px;overflow-y:auto;flex:1;min-height:0;">
|
|
3976
4409
|
<!-- Tutorials Section -->
|
|
3977
4410
|
<div style="margin-bottom:20px;">
|
|
3978
4411
|
<h3 style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--accent-blue);">Tutorials</h3>
|
|
@@ -4056,6 +4489,35 @@
|
|
|
4056
4489
|
</div>
|
|
4057
4490
|
`).join('');
|
|
4058
4491
|
}
|
|
4492
|
+
|
|
4493
|
+
// Wire search
|
|
4494
|
+
const helpSearchInput = document.getElementById('help-search-input');
|
|
4495
|
+
if (helpSearchInput) {
|
|
4496
|
+
let hst;
|
|
4497
|
+
helpSearchInput.addEventListener('input', () => {
|
|
4498
|
+
clearTimeout(hst);
|
|
4499
|
+
hst = setTimeout(() => {
|
|
4500
|
+
const q = helpSearchInput.value.toLowerCase().trim();
|
|
4501
|
+
// Filter feature categories
|
|
4502
|
+
panel.querySelectorAll('#help-feature-list > div').forEach(cat => {
|
|
4503
|
+
const items = cat.querySelectorAll('div:last-child > div');
|
|
4504
|
+
let any = false;
|
|
4505
|
+
items.forEach(item => {
|
|
4506
|
+
const m = !q || item.textContent.toLowerCase().includes(q);
|
|
4507
|
+
item.style.display = m ? '' : 'none';
|
|
4508
|
+
if (m) any = true;
|
|
4509
|
+
});
|
|
4510
|
+
cat.style.display = (!q || any) ? '' : 'none';
|
|
4511
|
+
// Auto-expand matching categories
|
|
4512
|
+
if (q && any) {
|
|
4513
|
+
const content = cat.querySelector('div:last-child');
|
|
4514
|
+
if (content) content.style.display = 'block';
|
|
4515
|
+
}
|
|
4516
|
+
});
|
|
4517
|
+
}, 150);
|
|
4518
|
+
});
|
|
4519
|
+
helpSearchInput.focus();
|
|
4520
|
+
}
|
|
4059
4521
|
});
|
|
4060
4522
|
|
|
4061
4523
|
// ? key opens help
|
|
@@ -4065,6 +4527,86 @@
|
|
|
4065
4527
|
}
|
|
4066
4528
|
});
|
|
4067
4529
|
|
|
4530
|
+
// ========== In-App Console (Ctrl+Shift+C) ==========
|
|
4531
|
+
(function initInAppConsole() {
|
|
4532
|
+
let consolePanel = null;
|
|
4533
|
+
const logs = [];
|
|
4534
|
+
const maxLogs = 200;
|
|
4535
|
+
const origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };
|
|
4536
|
+
|
|
4537
|
+
function capture(level, args) {
|
|
4538
|
+
const ts = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
4539
|
+
const msg = Array.from(args).map(a => {
|
|
4540
|
+
if (typeof a === 'string') return a;
|
|
4541
|
+
try { return JSON.stringify(a); } catch { return String(a); }
|
|
4542
|
+
}).join(' ');
|
|
4543
|
+
logs.push({ ts, level, msg });
|
|
4544
|
+
if (logs.length > maxLogs) logs.shift();
|
|
4545
|
+
if (consolePanel?.style.display !== 'none') renderConsoleLogs();
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
console.log = function() { origConsole.log.apply(console, arguments); capture('log', arguments); };
|
|
4549
|
+
console.warn = function() { origConsole.warn.apply(console, arguments); capture('warn', arguments); };
|
|
4550
|
+
console.error = function() { origConsole.error.apply(console, arguments); capture('error', arguments); };
|
|
4551
|
+
console.info = function() { origConsole.info.apply(console, arguments); capture('info', arguments); };
|
|
4552
|
+
|
|
4553
|
+
window.addEventListener('error', (e) => {
|
|
4554
|
+
capture('error', [`Uncaught: ${e.message} (${e.filename}:${e.lineno})`]);
|
|
4555
|
+
});
|
|
4556
|
+
window.addEventListener('unhandledrejection', (e) => {
|
|
4557
|
+
capture('error', [`Unhandled Promise: ${e.reason}`]);
|
|
4558
|
+
});
|
|
4559
|
+
|
|
4560
|
+
function renderConsoleLogs() {
|
|
4561
|
+
const body = consolePanel?.querySelector('#console-log-body');
|
|
4562
|
+
if (!body) return;
|
|
4563
|
+
body.innerHTML = logs.map(l => {
|
|
4564
|
+
const color = l.level === 'error' ? '#f85149' : l.level === 'warn' ? '#d29922' : l.level === 'info' ? '#58a6ff' : '#8b949e';
|
|
4565
|
+
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>`;
|
|
4566
|
+
}).join('');
|
|
4567
|
+
body.scrollTop = body.scrollHeight;
|
|
4568
|
+
}
|
|
4569
|
+
|
|
4570
|
+
function toggleConsole() {
|
|
4571
|
+
if (!consolePanel) {
|
|
4572
|
+
consolePanel = document.createElement('div');
|
|
4573
|
+
consolePanel.id = 'in-app-console';
|
|
4574
|
+
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;';
|
|
4575
|
+
consolePanel.innerHTML = `
|
|
4576
|
+
<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 12px;background:#161b22;border-bottom:1px solid #30363d;">
|
|
4577
|
+
<div style="display:flex;align-items:center;gap:12px;">
|
|
4578
|
+
<span style="font-weight:700;font-size:12px;color:#f0f0f0;">Console</span>
|
|
4579
|
+
<span id="console-error-count" style="font-size:10px;color:#f85149;"></span>
|
|
4580
|
+
</div>
|
|
4581
|
+
<div style="display:flex;gap:6px;">
|
|
4582
|
+
<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>
|
|
4583
|
+
<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>
|
|
4584
|
+
</div>
|
|
4585
|
+
</div>
|
|
4586
|
+
<div id="console-log-body" style="flex:1;overflow-y:auto;min-height:0;"></div>
|
|
4587
|
+
`;
|
|
4588
|
+
document.body.appendChild(consolePanel);
|
|
4589
|
+
} else {
|
|
4590
|
+
consolePanel.style.display = consolePanel.style.display === 'none' ? 'flex' : 'none';
|
|
4591
|
+
}
|
|
4592
|
+
renderConsoleLogs();
|
|
4593
|
+
const errCount = logs.filter(l => l.level === 'error').length;
|
|
4594
|
+
const ec = consolePanel.querySelector('#console-error-count');
|
|
4595
|
+
if (ec) ec.textContent = errCount ? `${errCount} errors` : '';
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
// Ctrl+Shift+C or backtick to open console
|
|
4599
|
+
document.addEventListener('keydown', (e) => {
|
|
4600
|
+
if ((e.ctrlKey && e.shiftKey && e.key === 'C') || (e.key === '`' && !e.ctrlKey && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA')) {
|
|
4601
|
+
e.preventDefault();
|
|
4602
|
+
toggleConsole();
|
|
4603
|
+
}
|
|
4604
|
+
});
|
|
4605
|
+
|
|
4606
|
+
// Expose for toolbar button
|
|
4607
|
+
window._toggleInAppConsole = toggleConsole;
|
|
4608
|
+
})();
|
|
4609
|
+
|
|
4068
4610
|
</script>
|
|
4069
4611
|
|
|
4070
4612
|
<!-- Operation Dialogs -->
|
|
@@ -4414,6 +4956,6 @@
|
|
|
4414
4956
|
</div>
|
|
4415
4957
|
</div>
|
|
4416
4958
|
|
|
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.
|
|
4959
|
+
<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
4960
|
</body>
|
|
4419
4961
|
</html>
|