cyclecad 1.1.1 → 1.2.0
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/index.html +30 -27
- package/app/js/ai-copilot.js +53 -18
- package/app/js/brep-engine.js +661 -0
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -1387,13 +1387,14 @@
|
|
|
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=120"></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=120"></script>
|
|
1393
1393
|
<script src="./js/connected-fabs.js"></script>
|
|
1394
|
-
<script type="module" src="./js/
|
|
1395
|
-
<script type="module" src="./js/
|
|
1396
|
-
<script type="module" src="./js/collaboration
|
|
1394
|
+
<script type="module" src="./js/brep-engine.js?v=120"></script>
|
|
1395
|
+
<script type="module" src="./js/ai-copilot.js?v=120"></script>
|
|
1396
|
+
<script type="module" src="./js/collaboration.js?v=120"></script>
|
|
1397
|
+
<script type="module" src="./js/collaboration-ui.js?v=120"></script>
|
|
1397
1398
|
<!-- CadXStudio-killer modules (IIFE, no imports) -->
|
|
1398
1399
|
<script src="./js/text-to-cad.js"></script>
|
|
1399
1400
|
<script src="./js/cam-operations.js"></script>
|
|
@@ -1419,7 +1420,7 @@
|
|
|
1419
1420
|
<span class="splash-logo-cycle">cycle</span><span class="splash-logo-cad">CAD</span>
|
|
1420
1421
|
</div>
|
|
1421
1422
|
<p class="splash-subtitle">Parametric 3D CAD Modeler for the Mechanical Designer</p>
|
|
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;">v1.
|
|
1423
|
+
<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;">v1.2.0</p>
|
|
1423
1424
|
</div>
|
|
1424
1425
|
<div class="splash-options">
|
|
1425
1426
|
<button class="splash-button splash-button-primary" id="btn-empty-project" style="grid-column: 1 / -1;">
|
|
@@ -1781,27 +1782,27 @@
|
|
|
1781
1782
|
<script type="module">
|
|
1782
1783
|
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
1783
1784
|
const _v = '50';
|
|
1784
|
-
import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, toggleWireframe as vpToggleWireframe, fitToObject } from './js/viewport.js?v=
|
|
1785
|
+
import { initViewport, setView, addToScene, removeFromScene, getScene, getCamera, getControls, toggleGrid as vpToggleGrid, toggleWireframe as vpToggleWireframe, fitToObject } from './js/viewport.js?v=120';
|
|
1785
1786
|
// fitAll defined locally to avoid import failures from cached viewport.js
|
|
1786
1787
|
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=
|
|
1788
|
-
import { extrudeProfile, createPrimitive, rebuildFeature, createMaterial } from './js/operations.js?v=
|
|
1789
|
-
import { initChat, parseCADPrompt, addMessage } from './js/ai-chat.js?v=
|
|
1790
|
-
import { initTree, addFeature, selectFeature, onSelect, removeFeature } from './js/tree.js?v=
|
|
1791
|
-
import { initParams, showParams, onParamChange } from './js/params.js?v=
|
|
1792
|
-
import { exportSTL, exportOBJ, exportJSON } from './js/export.js?v=
|
|
1793
|
-
import { initShortcuts } from './js/shortcuts.js?v=
|
|
1794
|
-
import { createReverseEngineerPanel, importFile, analyzeGeometry, reconstructFeatureTree, createWalkthrough } from './js/reverse-engineer.js?v=
|
|
1795
|
-
import { createInventorPanel, parseInventorFile } from './js/inventor-parser.js?v=
|
|
1796
|
-
import { loadProject, showFolderPicker, parseIPJ } from './js/project-loader.js?v=
|
|
1797
|
-
import { initProjectBrowser, showBrowser, hideBrowser, setProject, onFileSelect } from './js/project-browser.js?v=
|
|
1798
|
-
import { generateGuide, renderGuide, exportGuideHTML } from './js/rebuild-guide.js?v=
|
|
1799
|
-
import { solveConstraints, addConstraint, removeConstraint, autoDetectConstraints, isFullyConstrained, getAllConstraints, clearAllConstraints } from './js/constraint-solver.js?v=
|
|
1800
|
-
import { createSweep, createLoft, createBend, createFlange, createTab, createSlot, unfoldSheetMetal, createSpring, createThread } from './js/advanced-ops.js?v=
|
|
1801
|
-
import Assembly from './js/assembly.js?v=
|
|
1802
|
-
import { exportSketchToDXF, exportProjectionToDXF, exportMultiViewDXF, export3DDXF, downloadDXF } from './js/dxf-export.js?v=
|
|
1803
|
-
import { initAgentAPI } from './js/agent-api.js?v=
|
|
1804
|
-
import { initTokenDashboard } from './js/token-dashboard.js?v=
|
|
1788
|
+
import { startSketch, endSketch, setTool, getEntities, clearSketch } from './js/sketch.js?v=120';
|
|
1789
|
+
import { extrudeProfile, createPrimitive, rebuildFeature, createMaterial } from './js/operations.js?v=120';
|
|
1790
|
+
import { initChat, parseCADPrompt, addMessage } from './js/ai-chat.js?v=120';
|
|
1791
|
+
import { initTree, addFeature, selectFeature, onSelect, removeFeature } from './js/tree.js?v=120';
|
|
1792
|
+
import { initParams, showParams, onParamChange } from './js/params.js?v=120';
|
|
1793
|
+
import { exportSTL, exportOBJ, exportJSON } from './js/export.js?v=120';
|
|
1794
|
+
import { initShortcuts } from './js/shortcuts.js?v=120';
|
|
1795
|
+
import { createReverseEngineerPanel, importFile, analyzeGeometry, reconstructFeatureTree, createWalkthrough } from './js/reverse-engineer.js?v=120';
|
|
1796
|
+
import { createInventorPanel, parseInventorFile } from './js/inventor-parser.js?v=120';
|
|
1797
|
+
import { loadProject, showFolderPicker, parseIPJ } from './js/project-loader.js?v=120';
|
|
1798
|
+
import { initProjectBrowser, showBrowser, hideBrowser, setProject, onFileSelect } from './js/project-browser.js?v=120';
|
|
1799
|
+
import { generateGuide, renderGuide, exportGuideHTML } from './js/rebuild-guide.js?v=120';
|
|
1800
|
+
import { solveConstraints, addConstraint, removeConstraint, autoDetectConstraints, isFullyConstrained, getAllConstraints, clearAllConstraints } from './js/constraint-solver.js?v=120';
|
|
1801
|
+
import { createSweep, createLoft, createBend, createFlange, createTab, createSlot, unfoldSheetMetal, createSpring, createThread } from './js/advanced-ops.js?v=120';
|
|
1802
|
+
import Assembly from './js/assembly.js?v=120';
|
|
1803
|
+
import { exportSketchToDXF, exportProjectionToDXF, exportMultiViewDXF, export3DDXF, downloadDXF } from './js/dxf-export.js?v=120';
|
|
1804
|
+
import { initAgentAPI } from './js/agent-api.js?v=120';
|
|
1805
|
+
import { initTokenDashboard } from './js/token-dashboard.js?v=120';
|
|
1805
1806
|
|
|
1806
1807
|
// ========== Application State ==========
|
|
1807
1808
|
const APP = {
|
|
@@ -2015,7 +2016,7 @@
|
|
|
2015
2016
|
|
|
2016
2017
|
// Initialize Agent API — the primary interface
|
|
2017
2018
|
await tryStepAsync('agentAPI', async () => {
|
|
2018
|
-
const agentImports = await import('./js/agent-api.js?v=
|
|
2019
|
+
const agentImports = await import('./js/agent-api.js?v=120');
|
|
2019
2020
|
const agentSession = initAgentAPI({
|
|
2020
2021
|
viewport: {
|
|
2021
2022
|
getCamera,
|
|
@@ -2887,6 +2888,8 @@
|
|
|
2887
2888
|
|
|
2888
2889
|
// Expose globally so Copilot + Agent API + tutorials can create geometry
|
|
2889
2890
|
window._executeParsedPrompt = function(prompt) { executeParsedPrompt(prompt); };
|
|
2891
|
+
window.addToScene = addToScene;
|
|
2892
|
+
window.getScene = getScene;
|
|
2890
2893
|
|
|
2891
2894
|
function executeParsedPrompt(prompt) {
|
|
2892
2895
|
try {
|
|
@@ -5272,6 +5275,6 @@
|
|
|
5272
5275
|
</div>
|
|
5273
5276
|
</div>
|
|
5274
5277
|
|
|
5275
|
-
<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 v1.
|
|
5278
|
+
<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 v1.2.0</span>
|
|
5276
5279
|
</body>
|
|
5277
5280
|
</html>
|
package/app/js/ai-copilot.js
CHANGED
|
@@ -947,19 +947,62 @@ export async function executeTextCommand(prompt) {
|
|
|
947
947
|
|
|
948
948
|
addMessage('ai', `Got it! ${preview}. Creating now...`);
|
|
949
949
|
|
|
950
|
-
//
|
|
950
|
+
// ── B-REP ENGINE PATH ──────────────────────────────────────────────
|
|
951
|
+
// Try real OpenCascade.js B-rep first. Falls back to mesh preview.
|
|
952
|
+
const brep = window.brepEngine;
|
|
953
|
+
const brepCommands = commands.map(cmd => {
|
|
954
|
+
const method = cmd.method || '';
|
|
955
|
+
const type = method.replace('shape.', '').replace('feature.', '');
|
|
956
|
+
return { type, params: { ...cmd.params } };
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
// Attempt B-rep execution
|
|
960
|
+
if (brep) {
|
|
961
|
+
try {
|
|
962
|
+
if (!brep.isReady()) {
|
|
963
|
+
addMessage('ai', '⏳ Loading OpenCascade.js B-rep kernel (~50MB WASM)... first time only.');
|
|
964
|
+
await brep.initBRep();
|
|
965
|
+
addMessage('ai', '✅ B-rep kernel loaded!');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Remove previous B-rep mesh from scene
|
|
969
|
+
if (window._brepMesh) {
|
|
970
|
+
if (window._brepMesh.mesh?.parent) window._brepMesh.mesh.parent.remove(window._brepMesh.mesh);
|
|
971
|
+
if (window._brepMesh.wireframe?.parent) window._brepMesh.wireframe.parent.remove(window._brepMesh.wireframe);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const result = await brep.executeCommands(brepCommands);
|
|
975
|
+
if (result && result.mesh) {
|
|
976
|
+
// Add to Three.js scene
|
|
977
|
+
const addFn = window.addToScene || ((m) => {
|
|
978
|
+
const scene = window._scene || window.getScene?.();
|
|
979
|
+
if (scene) scene.add(m);
|
|
980
|
+
});
|
|
981
|
+
addFn(result.mesh);
|
|
982
|
+
addFn(result.wireframe);
|
|
983
|
+
window._brepMesh = result;
|
|
984
|
+
|
|
985
|
+
addMessage('ai', `🔧 **Real B-rep**: ${result.description} — solid model with true edges and faces.`);
|
|
986
|
+
return { ok: true, brep: true, description: result.description };
|
|
987
|
+
}
|
|
988
|
+
} catch (brepErr) {
|
|
989
|
+
console.warn('[Copilot] B-rep execution failed, falling back to mesh preview:', brepErr);
|
|
990
|
+
addMessage('ai', `⚠️ B-rep kernel error: ${brepErr.message}. Using mesh preview instead.`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// ── MESH PREVIEW FALLBACK ──────────────────────────────────────────
|
|
951
995
|
const results = [];
|
|
952
996
|
for (const cmd of commands) {
|
|
953
997
|
try {
|
|
954
|
-
// Convert Agent API format {method: 'shape.cylinder', params: {}} to executeParsedPrompt format {type: 'cylinder', params: {}}
|
|
955
998
|
if (window._executeParsedPrompt) {
|
|
956
999
|
const method = cmd.method || '';
|
|
957
1000
|
const type = method.replace('shape.', '').replace('feature.', '');
|
|
958
1001
|
|
|
959
|
-
// Operations that modify existing geometry
|
|
1002
|
+
// Operations that modify existing geometry
|
|
960
1003
|
const modifyOps = ['fillet', 'chamfer', 'pattern', 'mirror', 'shell'];
|
|
961
1004
|
if (modifyOps.includes(type)) {
|
|
962
|
-
addMessage('ai', `⚡ ${type}
|
|
1005
|
+
addMessage('ai', `⚡ ${type} — install B-rep kernel for real edge operations. Using visual preview.`);
|
|
963
1006
|
results.push({ ok: true, method, note: 'modify-op' });
|
|
964
1007
|
continue;
|
|
965
1008
|
}
|
|
@@ -968,23 +1011,15 @@ export async function executeTextCommand(prompt) {
|
|
|
968
1011
|
const count = cmd.params?.count || 1;
|
|
969
1012
|
for (let ci = 0; ci < count; ci++) {
|
|
970
1013
|
const p = Object.assign({}, cmd.params);
|
|
971
|
-
// Position holes at 4 corners of a typical cube face
|
|
972
1014
|
if (count > 1 && type === 'hole') {
|
|
973
|
-
const cornerSpread = 3.5;
|
|
974
|
-
const corners = [
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
[ cornerSpread, cornerSpread],
|
|
978
|
-
[-cornerSpread, cornerSpread],
|
|
979
|
-
];
|
|
980
|
-
const idx = ci % corners.length;
|
|
981
|
-
p._offsetX = corners[idx][0];
|
|
982
|
-
p._offsetZ = corners[idx][1];
|
|
1015
|
+
const cornerSpread = 3.5;
|
|
1016
|
+
const corners = [[-cornerSpread, -cornerSpread], [cornerSpread, -cornerSpread], [cornerSpread, cornerSpread], [-cornerSpread, cornerSpread]];
|
|
1017
|
+
p._offsetX = corners[ci % 4][0];
|
|
1018
|
+
p._offsetZ = corners[ci % 4][1];
|
|
983
1019
|
} else if (count > 1) {
|
|
984
1020
|
const angle = (ci / count) * Math.PI * 2;
|
|
985
|
-
|
|
986
|
-
p.
|
|
987
|
-
p._offsetZ = Math.sin(angle) * spread;
|
|
1021
|
+
p._offsetX = Math.cos(angle) * (p.radius || 5) * 3 * 0.1;
|
|
1022
|
+
p._offsetZ = Math.sin(angle) * (p.radius || 5) * 3 * 0.1;
|
|
988
1023
|
}
|
|
989
1024
|
window._executeParsedPrompt({ type, params: p });
|
|
990
1025
|
}
|
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* brep-engine.js — Real B-rep kernel for cycleCAD using OpenCascade.js
|
|
3
|
+
*
|
|
4
|
+
* Provides true solid modeling operations:
|
|
5
|
+
* - Primitive creation (box, cylinder, sphere, cone)
|
|
6
|
+
* - Boolean operations (cut, fuse, intersect)
|
|
7
|
+
* - Fillet and chamfer on real edges
|
|
8
|
+
* - Shape → Three.js BufferGeometry conversion
|
|
9
|
+
*
|
|
10
|
+
* Uses opencascade.js v2.0.0-beta (modular WASM build)
|
|
11
|
+
*
|
|
12
|
+
* MIT License — cycleCAD (c) 2026
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// STATE
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
let oc = null; // OpenCascade.js instance
|
|
20
|
+
let _loading = false; // Prevent double-init
|
|
21
|
+
let _ready = false; // True when WASM is loaded
|
|
22
|
+
let _currentShape = null; // Active TopoDS_Shape (the "document")
|
|
23
|
+
const _shapeStack = []; // Undo stack of shapes
|
|
24
|
+
|
|
25
|
+
// CDN for opencascade.js 2.0.0-beta
|
|
26
|
+
const OCC_CDN = 'https://unpkg.com/opencascade.js@2.0.0-beta.533428a/dist/';
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// INITIALIZATION
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load and initialize the OpenCascade.js WASM kernel.
|
|
34
|
+
* Returns a promise that resolves when ready.
|
|
35
|
+
* Subsequent calls return immediately if already loaded.
|
|
36
|
+
*/
|
|
37
|
+
export async function initBRep() {
|
|
38
|
+
if (_ready && oc) return oc;
|
|
39
|
+
if (_loading) {
|
|
40
|
+
// Wait for in-progress load
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const check = setInterval(() => {
|
|
43
|
+
if (_ready && oc) { clearInterval(check); resolve(oc); }
|
|
44
|
+
}, 200);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_loading = true;
|
|
49
|
+
console.log('[BRep] Loading OpenCascade.js WASM kernel...');
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Dynamic import from CDN
|
|
53
|
+
const module = await import(/* webpackIgnore: true */ OCC_CDN + 'opencascade.full.js');
|
|
54
|
+
const factory = module.default || module;
|
|
55
|
+
|
|
56
|
+
// Initialize WASM — factory returns a promise
|
|
57
|
+
oc = await factory({
|
|
58
|
+
locateFile: (file) => OCC_CDN + file,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
_ready = true;
|
|
62
|
+
_loading = false;
|
|
63
|
+
console.log('[BRep] OpenCascade.js ready ✓');
|
|
64
|
+
return oc;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error('[BRep] Failed to load OpenCascade.js:', err);
|
|
67
|
+
_loading = false;
|
|
68
|
+
|
|
69
|
+
// Fallback: try loading via script tag
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
console.log('[BRep] Trying script tag fallback...');
|
|
72
|
+
const savedModule = window.Module;
|
|
73
|
+
const script = document.createElement('script');
|
|
74
|
+
script.src = OCC_CDN + 'opencascade.full.js';
|
|
75
|
+
script.onload = async () => {
|
|
76
|
+
try {
|
|
77
|
+
const occFactory = window.Module;
|
|
78
|
+
window.Module = savedModule; // restore
|
|
79
|
+
oc = await new occFactory({
|
|
80
|
+
locateFile: (file) => OCC_CDN + file,
|
|
81
|
+
});
|
|
82
|
+
_ready = true;
|
|
83
|
+
_loading = false;
|
|
84
|
+
console.log('[BRep] OpenCascade.js ready (script fallback) ✓');
|
|
85
|
+
resolve(oc);
|
|
86
|
+
} catch (e2) {
|
|
87
|
+
_loading = false;
|
|
88
|
+
reject(e2);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
script.onerror = () => { _loading = false; reject(new Error('Script load failed')); };
|
|
92
|
+
document.head.appendChild(script);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Check if the B-rep kernel is ready */
|
|
98
|
+
export function isReady() { return _ready && oc !== null; }
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// HELPER: gp_Pnt constructor
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
function pnt(x, y, z) {
|
|
105
|
+
return new oc.gp_Pnt_3(x, y, z);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function ax2(origin, dir) {
|
|
109
|
+
return new oc.gp_Ax2_3(
|
|
110
|
+
pnt(origin[0], origin[1], origin[2]),
|
|
111
|
+
new oc.gp_Dir_4(dir[0], dir[1], dir[2])
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function progress() {
|
|
116
|
+
return new oc.Message_ProgressRange_1();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// PRIMITIVES
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create a B-rep box centered at origin
|
|
125
|
+
* @param {number} width - X dimension in mm
|
|
126
|
+
* @param {number} height - Y dimension in mm
|
|
127
|
+
* @param {number} depth - Z dimension in mm
|
|
128
|
+
* @returns {TopoDS_Shape}
|
|
129
|
+
*/
|
|
130
|
+
export function makeBox(width, height, depth) {
|
|
131
|
+
const w = width, h = height, d = depth;
|
|
132
|
+
// Create box at (-w/2, -h/2, -d/2) so it's centered
|
|
133
|
+
const builder = new oc.BRepPrimAPI_MakeBox_3(
|
|
134
|
+
pnt(-w / 2, -h / 2, -d / 2), w, h, d
|
|
135
|
+
);
|
|
136
|
+
const shape = builder.Shape();
|
|
137
|
+
builder.delete();
|
|
138
|
+
return shape;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a B-rep cylinder centered at origin, axis along Y
|
|
143
|
+
* @param {number} radius - Radius in mm
|
|
144
|
+
* @param {number} height - Height in mm
|
|
145
|
+
* @returns {TopoDS_Shape}
|
|
146
|
+
*/
|
|
147
|
+
export function makeCylinder(radius, height) {
|
|
148
|
+
const axis = ax2([0, -height / 2, 0], [0, 1, 0]);
|
|
149
|
+
const builder = new oc.BRepPrimAPI_MakeCylinder_2(axis, radius, height);
|
|
150
|
+
const shape = builder.Shape();
|
|
151
|
+
builder.delete();
|
|
152
|
+
return shape;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create a B-rep sphere centered at origin
|
|
157
|
+
* @param {number} radius - Radius in mm
|
|
158
|
+
* @returns {TopoDS_Shape}
|
|
159
|
+
*/
|
|
160
|
+
export function makeSphere(radius) {
|
|
161
|
+
const builder = new oc.BRepPrimAPI_MakeSphere_1(radius);
|
|
162
|
+
const shape = builder.Shape();
|
|
163
|
+
builder.delete();
|
|
164
|
+
return shape;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Create a B-rep cone centered at origin, axis along Y
|
|
169
|
+
* @param {number} bottomRadius
|
|
170
|
+
* @param {number} topRadius
|
|
171
|
+
* @param {number} height
|
|
172
|
+
* @returns {TopoDS_Shape}
|
|
173
|
+
*/
|
|
174
|
+
export function makeCone(bottomRadius, topRadius, height) {
|
|
175
|
+
const axis = ax2([0, -height / 2, 0], [0, 1, 0]);
|
|
176
|
+
const builder = new oc.BRepPrimAPI_MakeCone_2(axis, bottomRadius, topRadius, height);
|
|
177
|
+
const shape = builder.Shape();
|
|
178
|
+
builder.delete();
|
|
179
|
+
return shape;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// BOOLEAN OPERATIONS
|
|
184
|
+
// ============================================================================
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Boolean cut: result = base - tool
|
|
188
|
+
* @param {TopoDS_Shape} base - Shape to cut from
|
|
189
|
+
* @param {TopoDS_Shape} tool - Shape to subtract
|
|
190
|
+
* @returns {TopoDS_Shape}
|
|
191
|
+
*/
|
|
192
|
+
export function booleanCut(base, tool) {
|
|
193
|
+
const cutter = new oc.BRepAlgoAPI_Cut_3(base, tool, progress());
|
|
194
|
+
cutter.Build(progress());
|
|
195
|
+
if (!cutter.IsDone()) {
|
|
196
|
+
console.warn('[BRep] Boolean cut failed');
|
|
197
|
+
cutter.delete();
|
|
198
|
+
return base;
|
|
199
|
+
}
|
|
200
|
+
const result = cutter.Shape();
|
|
201
|
+
cutter.delete();
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Boolean fuse (union): result = base + tool
|
|
207
|
+
* @param {TopoDS_Shape} base
|
|
208
|
+
* @param {TopoDS_Shape} tool
|
|
209
|
+
* @returns {TopoDS_Shape}
|
|
210
|
+
*/
|
|
211
|
+
export function booleanFuse(base, tool) {
|
|
212
|
+
const fuser = new oc.BRepAlgoAPI_Fuse_3(base, tool, progress());
|
|
213
|
+
fuser.Build(progress());
|
|
214
|
+
if (!fuser.IsDone()) {
|
|
215
|
+
console.warn('[BRep] Boolean fuse failed');
|
|
216
|
+
fuser.delete();
|
|
217
|
+
return base;
|
|
218
|
+
}
|
|
219
|
+
const result = fuser.Shape();
|
|
220
|
+
fuser.delete();
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Boolean intersect: result = base ∩ tool
|
|
226
|
+
* @param {TopoDS_Shape} base
|
|
227
|
+
* @param {TopoDS_Shape} tool
|
|
228
|
+
* @returns {TopoDS_Shape}
|
|
229
|
+
*/
|
|
230
|
+
export function booleanIntersect(base, tool) {
|
|
231
|
+
const common = new oc.BRepAlgoAPI_Common_3(base, tool, progress());
|
|
232
|
+
common.Build(progress());
|
|
233
|
+
if (!common.IsDone()) {
|
|
234
|
+
console.warn('[BRep] Boolean intersect failed');
|
|
235
|
+
common.delete();
|
|
236
|
+
return base;
|
|
237
|
+
}
|
|
238
|
+
const result = common.Shape();
|
|
239
|
+
common.delete();
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// FILLET & CHAMFER
|
|
245
|
+
// ============================================================================
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Apply fillet (round) to ALL edges of a shape
|
|
249
|
+
* @param {TopoDS_Shape} shape - Input shape
|
|
250
|
+
* @param {number} radius - Fillet radius in mm
|
|
251
|
+
* @returns {TopoDS_Shape}
|
|
252
|
+
*/
|
|
253
|
+
export function filletAll(shape, radius) {
|
|
254
|
+
const fillet = new oc.BRepFilletAPI_MakeFillet(shape, oc.ChFi3d_Rational);
|
|
255
|
+
const explorer = new oc.TopExp_Explorer_2(shape, oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
|
|
256
|
+
|
|
257
|
+
let edgeCount = 0;
|
|
258
|
+
while (explorer.More()) {
|
|
259
|
+
const edge = oc.TopoDS.Edge_1(explorer.Current());
|
|
260
|
+
fillet.Add_2(radius, edge);
|
|
261
|
+
edgeCount++;
|
|
262
|
+
explorer.Next();
|
|
263
|
+
}
|
|
264
|
+
explorer.delete();
|
|
265
|
+
|
|
266
|
+
if (edgeCount === 0) {
|
|
267
|
+
console.warn('[BRep] No edges found for fillet');
|
|
268
|
+
fillet.delete();
|
|
269
|
+
return shape;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const result = fillet.Shape();
|
|
274
|
+
console.log(`[BRep] Fillet applied to ${edgeCount} edges (r=${radius}mm)`);
|
|
275
|
+
fillet.delete();
|
|
276
|
+
return result;
|
|
277
|
+
} catch (e) {
|
|
278
|
+
console.warn('[BRep] Fillet failed (radius may be too large):', e.message);
|
|
279
|
+
fillet.delete();
|
|
280
|
+
return shape;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Apply chamfer to ALL edges of a shape
|
|
286
|
+
* @param {TopoDS_Shape} shape - Input shape
|
|
287
|
+
* @param {number} distance - Chamfer distance in mm
|
|
288
|
+
* @returns {TopoDS_Shape}
|
|
289
|
+
*/
|
|
290
|
+
export function chamferAll(shape, distance) {
|
|
291
|
+
const chamfer = new oc.BRepFilletAPI_MakeChamfer(shape);
|
|
292
|
+
const explorer = new oc.TopExp_Explorer_2(shape, oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
|
|
293
|
+
|
|
294
|
+
let edgeCount = 0;
|
|
295
|
+
while (explorer.More()) {
|
|
296
|
+
const edge = oc.TopoDS.Edge_1(explorer.Current());
|
|
297
|
+
chamfer.Add_2(distance, edge);
|
|
298
|
+
edgeCount++;
|
|
299
|
+
explorer.Next();
|
|
300
|
+
}
|
|
301
|
+
explorer.delete();
|
|
302
|
+
|
|
303
|
+
if (edgeCount === 0) {
|
|
304
|
+
chamfer.delete();
|
|
305
|
+
return shape;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const result = chamfer.Shape();
|
|
310
|
+
console.log(`[BRep] Chamfer applied to ${edgeCount} edges (d=${distance}mm)`);
|
|
311
|
+
chamfer.delete();
|
|
312
|
+
return result;
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.warn('[BRep] Chamfer failed:', e.message);
|
|
315
|
+
chamfer.delete();
|
|
316
|
+
return shape;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// TRANSFORM
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Translate a shape by (dx, dy, dz)
|
|
326
|
+
* @param {TopoDS_Shape} shape
|
|
327
|
+
* @param {number} dx
|
|
328
|
+
* @param {number} dy
|
|
329
|
+
* @param {number} dz
|
|
330
|
+
* @returns {TopoDS_Shape}
|
|
331
|
+
*/
|
|
332
|
+
export function translate(shape, dx, dy, dz) {
|
|
333
|
+
const trsf = new oc.gp_Trsf_1();
|
|
334
|
+
trsf.SetTranslation_1(new oc.gp_Vec_4(dx, dy, dz));
|
|
335
|
+
const transform = new oc.BRepBuilderAPI_Transform_2(shape, trsf, true);
|
|
336
|
+
const result = transform.Shape();
|
|
337
|
+
transform.delete();
|
|
338
|
+
trsf.delete();
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// SHAPE → THREE.JS CONVERSION
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Convert a TopoDS_Shape to Three.js BufferGeometry
|
|
348
|
+
* Tessellates the B-rep surface and extracts vertex/normal/index arrays
|
|
349
|
+
*
|
|
350
|
+
* @param {TopoDS_Shape} shape - OCC shape to convert
|
|
351
|
+
* @param {number} deflection - Mesh quality (smaller = finer). Default 0.5mm
|
|
352
|
+
* @returns {THREE.BufferGeometry}
|
|
353
|
+
*/
|
|
354
|
+
export function shapeToGeometry(shape, deflection = 0.5) {
|
|
355
|
+
// Tessellate the shape
|
|
356
|
+
new oc.BRepMesh_IncrementalMesh_2(shape, deflection, false, deflection * 5, false);
|
|
357
|
+
|
|
358
|
+
const positions = [];
|
|
359
|
+
const normals = [];
|
|
360
|
+
const indices = [];
|
|
361
|
+
let vertexOffset = 0;
|
|
362
|
+
|
|
363
|
+
// Iterate over all faces
|
|
364
|
+
const faceExplorer = new oc.TopExp_Explorer_2(
|
|
365
|
+
shape,
|
|
366
|
+
oc.TopAbs_ShapeEnum.TopAbs_FACE,
|
|
367
|
+
oc.TopAbs_ShapeEnum.TopAbs_SHAPE
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
while (faceExplorer.More()) {
|
|
371
|
+
const face = oc.TopoDS.Face_1(faceExplorer.Current());
|
|
372
|
+
const location = new oc.TopLoc_Location_1();
|
|
373
|
+
const triangulation = oc.BRep_Tool.Triangulation(face, location);
|
|
374
|
+
|
|
375
|
+
if (!triangulation.IsNull()) {
|
|
376
|
+
const tri = triangulation.get();
|
|
377
|
+
const nbNodes = tri.NbNodes();
|
|
378
|
+
const nbTriangles = tri.NbTriangles();
|
|
379
|
+
|
|
380
|
+
// Get the transformation from the face location
|
|
381
|
+
const trsf = location.Transformation();
|
|
382
|
+
|
|
383
|
+
// Extract vertices
|
|
384
|
+
for (let i = 1; i <= nbNodes; i++) {
|
|
385
|
+
const node = tri.Node(i);
|
|
386
|
+
const transformed = node.Transformed(trsf);
|
|
387
|
+
positions.push(transformed.X(), transformed.Y(), transformed.Z());
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Compute face normal orientation
|
|
391
|
+
const orientation = face.Orientation_1();
|
|
392
|
+
const reversed = (orientation === oc.TopAbs_Orientation.TopAbs_REVERSED);
|
|
393
|
+
|
|
394
|
+
// Extract triangles
|
|
395
|
+
for (let i = 1; i <= nbTriangles; i++) {
|
|
396
|
+
const triangle = tri.Triangle(i);
|
|
397
|
+
let n1 = triangle.Value(1);
|
|
398
|
+
let n2 = triangle.Value(2);
|
|
399
|
+
let n3 = triangle.Value(3);
|
|
400
|
+
|
|
401
|
+
// Flip winding if face is reversed
|
|
402
|
+
if (reversed) { [n2, n3] = [n3, n2]; }
|
|
403
|
+
|
|
404
|
+
indices.push(
|
|
405
|
+
vertexOffset + n1 - 1,
|
|
406
|
+
vertexOffset + n2 - 1,
|
|
407
|
+
vertexOffset + n3 - 1
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
vertexOffset += nbNodes;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
location.delete();
|
|
415
|
+
faceExplorer.Next();
|
|
416
|
+
}
|
|
417
|
+
faceExplorer.delete();
|
|
418
|
+
|
|
419
|
+
// Build Three.js BufferGeometry
|
|
420
|
+
const geometry = new THREE.BufferGeometry();
|
|
421
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
422
|
+
geometry.setIndex(indices);
|
|
423
|
+
geometry.computeVertexNormals();
|
|
424
|
+
|
|
425
|
+
return geometry;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Convert a TopoDS_Shape to a full Three.js Mesh with material
|
|
430
|
+
*
|
|
431
|
+
* @param {TopoDS_Shape} shape - OCC shape
|
|
432
|
+
* @param {object} options - { color, metalness, roughness, opacity, wireframe }
|
|
433
|
+
* @param {number} deflection - Mesh quality
|
|
434
|
+
* @returns {{ mesh: THREE.Mesh, wireframe: THREE.LineSegments, shape: TopoDS_Shape }}
|
|
435
|
+
*/
|
|
436
|
+
export function shapeToMesh(shape, options = {}, deflection = 0.5) {
|
|
437
|
+
const {
|
|
438
|
+
color = 0x4488cc,
|
|
439
|
+
metalness = 0.3,
|
|
440
|
+
roughness = 0.6,
|
|
441
|
+
opacity = 1.0,
|
|
442
|
+
wireframe = false,
|
|
443
|
+
} = options;
|
|
444
|
+
|
|
445
|
+
const geometry = shapeToGeometry(shape, deflection);
|
|
446
|
+
|
|
447
|
+
const material = new THREE.MeshStandardMaterial({
|
|
448
|
+
color,
|
|
449
|
+
metalness,
|
|
450
|
+
roughness,
|
|
451
|
+
transparent: opacity < 1,
|
|
452
|
+
opacity,
|
|
453
|
+
side: THREE.DoubleSide,
|
|
454
|
+
wireframe,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
458
|
+
mesh.castShadow = true;
|
|
459
|
+
mesh.receiveShadow = true;
|
|
460
|
+
|
|
461
|
+
// Wireframe edges overlay
|
|
462
|
+
const edges = new THREE.EdgesGeometry(geometry, 15);
|
|
463
|
+
const lineMat = new THREE.LineBasicMaterial({ color: 0x000000, opacity: 0.3, transparent: true });
|
|
464
|
+
const wireframeLines = new THREE.LineSegments(edges, lineMat);
|
|
465
|
+
|
|
466
|
+
return { mesh, wireframe: wireframeLines, shape };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ============================================================================
|
|
470
|
+
// HIGH-LEVEL COPILOT API
|
|
471
|
+
// ============================================================================
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Execute a full copilot command sequence using real B-rep operations.
|
|
475
|
+
* Manages the current shape state and converts to Three.js.
|
|
476
|
+
*
|
|
477
|
+
* @param {Array} commands - Array of { type, params } objects
|
|
478
|
+
* @returns {Promise<{ mesh, wireframe, shape, description }>}
|
|
479
|
+
*/
|
|
480
|
+
export async function executeCommands(commands) {
|
|
481
|
+
if (!_ready) {
|
|
482
|
+
await initBRep();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const SCALE = 0.1; // mm to scene units
|
|
486
|
+
let description = '';
|
|
487
|
+
|
|
488
|
+
for (const cmd of commands) {
|
|
489
|
+
const { type, params = {} } = cmd;
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
switch (type) {
|
|
493
|
+
case 'box': {
|
|
494
|
+
const w = params.width || 100;
|
|
495
|
+
const h = params.height || 100;
|
|
496
|
+
const d = params.depth || 100;
|
|
497
|
+
_pushUndo();
|
|
498
|
+
_currentShape = makeBox(w, h, d);
|
|
499
|
+
description += `Box ${w}×${h}×${d}mm`;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
case 'cylinder': {
|
|
504
|
+
const r = params.radius || 25;
|
|
505
|
+
const h = params.height || 50;
|
|
506
|
+
_pushUndo();
|
|
507
|
+
_currentShape = makeCylinder(r, h);
|
|
508
|
+
description += `Cylinder r${r} h${h}mm`;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
case 'sphere': {
|
|
513
|
+
const r = params.radius || 25;
|
|
514
|
+
_pushUndo();
|
|
515
|
+
_currentShape = makeSphere(r);
|
|
516
|
+
description += `Sphere r${r}mm`;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
case 'cone': {
|
|
521
|
+
const br = params.bottomRadius || params.radius || 25;
|
|
522
|
+
const tr = params.topRadius || 0;
|
|
523
|
+
const h = params.height || 50;
|
|
524
|
+
_pushUndo();
|
|
525
|
+
_currentShape = makeCone(br, tr, h);
|
|
526
|
+
description += `Cone r${br}/${tr} h${h}mm`;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
case 'hole': {
|
|
531
|
+
if (!_currentShape) {
|
|
532
|
+
console.warn('[BRep] No shape to cut hole in — create a shape first');
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
const r = params.radius || 5;
|
|
536
|
+
const depth = params.depth || params.height || 200;
|
|
537
|
+
const count = params.count || 1;
|
|
538
|
+
|
|
539
|
+
_pushUndo();
|
|
540
|
+
|
|
541
|
+
for (let i = 0; i < count; i++) {
|
|
542
|
+
// Position holes at corners for count=4, or center for count=1
|
|
543
|
+
let dx = 0, dz = 0;
|
|
544
|
+
if (count === 4) {
|
|
545
|
+
const spread = (params.spread || 30);
|
|
546
|
+
const corners = [[-spread, -spread], [spread, -spread], [spread, spread], [-spread, spread]];
|
|
547
|
+
[dx, dz] = corners[i % 4];
|
|
548
|
+
} else if (count > 1) {
|
|
549
|
+
const angle = (i / count) * Math.PI * 2;
|
|
550
|
+
const spread = (params.spread || 20);
|
|
551
|
+
dx = Math.cos(angle) * spread;
|
|
552
|
+
dz = Math.sin(angle) * spread;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Create cylinder tool at position, axis along Y
|
|
556
|
+
let tool = makeCylinder(r, depth);
|
|
557
|
+
if (dx !== 0 || dz !== 0) {
|
|
558
|
+
tool = translate(tool, dx, 0, dz);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
_currentShape = booleanCut(_currentShape, tool);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
description += ` + ${count} hole${count > 1 ? 's' : ''} (r${r}mm)`;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
case 'fillet': {
|
|
569
|
+
if (!_currentShape) break;
|
|
570
|
+
const r = params.radius || 5;
|
|
571
|
+
_pushUndo();
|
|
572
|
+
_currentShape = filletAll(_currentShape, r);
|
|
573
|
+
description += ` + fillet r${r}mm`;
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
case 'chamfer': {
|
|
578
|
+
if (!_currentShape) break;
|
|
579
|
+
const d = params.distance || 3;
|
|
580
|
+
_pushUndo();
|
|
581
|
+
_currentShape = chamferAll(_currentShape, d);
|
|
582
|
+
description += ` + chamfer ${d}mm`;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
case 'fuse': {
|
|
587
|
+
if (!_currentShape || !params._toolShape) break;
|
|
588
|
+
_pushUndo();
|
|
589
|
+
_currentShape = booleanFuse(_currentShape, params._toolShape);
|
|
590
|
+
description += ' + fuse';
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
default:
|
|
595
|
+
console.warn(`[BRep] Unknown command type: ${type}`);
|
|
596
|
+
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
console.error(`[BRep] Command "${type}" failed:`, err);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Convert final shape to Three.js
|
|
603
|
+
if (!_currentShape) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const result = shapeToMesh(_currentShape, { color: 0x4488cc }, 0.5);
|
|
608
|
+
// Scale mesh to scene units
|
|
609
|
+
result.mesh.scale.set(SCALE, SCALE, SCALE);
|
|
610
|
+
result.wireframe.scale.set(SCALE, SCALE, SCALE);
|
|
611
|
+
result.description = description;
|
|
612
|
+
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ============================================================================
|
|
617
|
+
// UNDO SUPPORT
|
|
618
|
+
// ============================================================================
|
|
619
|
+
|
|
620
|
+
function _pushUndo() {
|
|
621
|
+
if (_currentShape) {
|
|
622
|
+
_shapeStack.push(_currentShape);
|
|
623
|
+
if (_shapeStack.length > 20) _shapeStack.shift(); // limit memory
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export function undo() {
|
|
628
|
+
if (_shapeStack.length > 0) {
|
|
629
|
+
_currentShape = _shapeStack.pop();
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export function getCurrentShape() { return _currentShape; }
|
|
636
|
+
export function clearShape() { _currentShape = null; _shapeStack.length = 0; }
|
|
637
|
+
|
|
638
|
+
// ============================================================================
|
|
639
|
+
// REGISTER ON WINDOW FOR COPILOT ACCESS
|
|
640
|
+
// ============================================================================
|
|
641
|
+
|
|
642
|
+
window.brepEngine = {
|
|
643
|
+
initBRep,
|
|
644
|
+
isReady,
|
|
645
|
+
makeBox,
|
|
646
|
+
makeCylinder,
|
|
647
|
+
makeSphere,
|
|
648
|
+
makeCone,
|
|
649
|
+
booleanCut,
|
|
650
|
+
booleanFuse,
|
|
651
|
+
booleanIntersect,
|
|
652
|
+
filletAll,
|
|
653
|
+
chamferAll,
|
|
654
|
+
translate,
|
|
655
|
+
shapeToGeometry,
|
|
656
|
+
shapeToMesh,
|
|
657
|
+
executeCommands,
|
|
658
|
+
undo,
|
|
659
|
+
getCurrentShape,
|
|
660
|
+
clearShape,
|
|
661
|
+
};
|
package/package.json
CHANGED