cyclecad 3.10.3 → 3.11.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/.github/workflows/pages.yml +34 -0
- package/.nojekyll +0 -0
- package/CLAUDE.md +430 -0
- package/HANDOFF-2026-04-24-session-2.md +239 -0
- package/HANDOFF-2026-04-24.md +90 -0
- package/app/index.html +9 -0
- package/app/js/modules/ai-copilot.js +275 -2
- package/app/js/modules/ai-engineer.js +613 -0
- package/app/js/modules/pentacad-bridge.js +216 -0
- package/app/js/modules/pentacad-cam.js +184 -0
- package/app/js/modules/pentacad-sim.js +215 -0
- package/app/js/modules/pentacad.js +233 -0
- package/app/pentacad.html +240 -0
- package/index-agent-first.html.bak +1306 -0
- package/machines/v2-50-chb/kinematics.json +51 -0
- package/mockups/cyclecad-suite-mockup.html +1746 -0
- package/package.json +1 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file pentacad.js
|
|
3
|
+
* @description Pentacad — browser-based CAM + 5-axis simulator + machine control
|
|
4
|
+
* for the Pentamachine V2 line. Part of the cycleCAD Suite.
|
|
5
|
+
*
|
|
6
|
+
* This is the coordinator module. It registers sub-modules
|
|
7
|
+
* (pentacad-cam, pentacad-sim, pentacad-bridge) and exposes
|
|
8
|
+
* the unified Pentacad API surface on window.CycleCAD.Pentacad.
|
|
9
|
+
*
|
|
10
|
+
* @version 0.1.0
|
|
11
|
+
* @author Sachin Kumar <vvlars@googlemail.com>
|
|
12
|
+
* @license AGPL-3.0-only (dual-licensed: commercial available)
|
|
13
|
+
* @module pentacad
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
window.CycleCAD = window.CycleCAD || {};
|
|
19
|
+
|
|
20
|
+
window.CycleCAD.Pentacad = (() => {
|
|
21
|
+
const MODULE_NAME = 'pentacad';
|
|
22
|
+
const VERSION = '0.1.0';
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// STATE
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
const state = {
|
|
29
|
+
phase: 'scaffold', // scaffold | phase-0 | phase-1 | phase-2 | phase-3
|
|
30
|
+
machine: null, // loaded machine definition (kinematics, spindle, etc.)
|
|
31
|
+
activeMachineId: null, // 'v2-10' | 'v2-50-chb' | 'v2-50-chk'
|
|
32
|
+
workpiece: null, // reference to cycleCAD model being machined
|
|
33
|
+
setups: [], // 3+2 setups
|
|
34
|
+
operations: [], // CAM operations
|
|
35
|
+
toolpaths: [], // generated toolpaths
|
|
36
|
+
gcode: null, // last emitted G-code
|
|
37
|
+
bridgeStatus: 'disconnected',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const SUPPORTED_MACHINES = [
|
|
41
|
+
{ id: 'v2-10', name: 'Pentamachine V2-10', status: 'template' },
|
|
42
|
+
{ id: 'v2-50-chb', name: 'Pentamachine V2-50CHB', status: 'primary' },
|
|
43
|
+
{ id: 'v2-50-chk', name: 'Pentamachine V2-50CHK', status: 'template' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// MACHINE LOADER
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
async function loadMachine(machineId) {
|
|
51
|
+
const base = `/machines/${machineId}`;
|
|
52
|
+
try {
|
|
53
|
+
const [kin, spn, env, post, tools] = await Promise.all([
|
|
54
|
+
fetch(`${base}/kinematics.json`).then(r => r.ok ? r.json() : null),
|
|
55
|
+
fetch(`${base}/spindle.json`).then(r => r.ok ? r.json() : null),
|
|
56
|
+
fetch(`${base}/envelope.json`).then(r => r.ok ? r.json() : null),
|
|
57
|
+
fetch(`${base}/post.json`).then(r => r.ok ? r.json() : null),
|
|
58
|
+
fetch(`${base}/tool-library.json`).then(r => r.ok ? r.json() : null),
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
if (!kin) {
|
|
62
|
+
console.warn(`[Pentacad] No kinematics.json for ${machineId} — using defaults`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
state.machine = { id: machineId, kinematics: kin, spindle: spn, envelope: env, post, tools };
|
|
66
|
+
state.activeMachineId = machineId;
|
|
67
|
+
window.dispatchEvent(new CustomEvent('pentacad:machine-loaded', { detail: state.machine }));
|
|
68
|
+
console.log(`[Pentacad] Machine loaded: ${machineId}`, state.machine);
|
|
69
|
+
return state.machine;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error(`[Pentacad] Failed to load machine ${machineId}:`, err);
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// INIT — wires in sub-modules once they're available
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
function init(options = {}) {
|
|
81
|
+
const Cam = window.CycleCAD.PentacadCAM;
|
|
82
|
+
const Sim = window.CycleCAD.PentacadSim;
|
|
83
|
+
const Bridge = window.CycleCAD.PentacadBridge;
|
|
84
|
+
|
|
85
|
+
if (Cam?.init) Cam.init({ state, onEvent: emit });
|
|
86
|
+
if (Sim?.init) Sim.init({ state, onEvent: emit });
|
|
87
|
+
if (Bridge?.init) Bridge.init({ state, onEvent: emit });
|
|
88
|
+
|
|
89
|
+
console.log(
|
|
90
|
+
`%cPentacad v${VERSION} initialized`,
|
|
91
|
+
'color:#10b981;font-weight:bold'
|
|
92
|
+
);
|
|
93
|
+
console.log(` CAM: ${Cam ? 'loaded' : 'missing (pentacad-cam.js)'}`);
|
|
94
|
+
console.log(` Sim: ${Sim ? 'loaded' : 'missing (pentacad-sim.js)'}`);
|
|
95
|
+
console.log(` Bridge: ${Bridge ? 'loaded' : 'missing (pentacad-bridge.js)'}`);
|
|
96
|
+
return { version: VERSION, phase: state.phase };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function emit(eventName, detail) {
|
|
100
|
+
window.dispatchEvent(new CustomEvent(`pentacad:${eventName}`, { detail }));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// UI (returns a DOM element for embedding in the main cycleCAD app)
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
function getUI() {
|
|
108
|
+
const root = document.createElement('div');
|
|
109
|
+
root.className = 'pentacad-panel';
|
|
110
|
+
root.innerHTML = `
|
|
111
|
+
<style>
|
|
112
|
+
.pentacad-panel { padding: 12px; color: #e2e8f0; font-size: 13px; }
|
|
113
|
+
.pentacad-panel h3 { margin: 0 0 12px 0; font-size: 14px; color: #10b981; letter-spacing: 1px; }
|
|
114
|
+
.pentacad-panel .machine-picker { display: grid; gap: 4px; margin-bottom: 16px; }
|
|
115
|
+
.pentacad-panel .machine-picker button {
|
|
116
|
+
background: #1e293b; color: #cbd5e1; border: 1px solid #334155;
|
|
117
|
+
padding: 8px 12px; border-radius: 4px; cursor: pointer; text-align: left;
|
|
118
|
+
}
|
|
119
|
+
.pentacad-panel .machine-picker button:hover { background: #334155; }
|
|
120
|
+
.pentacad-panel .machine-picker button.active { background: #10b981; color: #0f172a; border-color: #10b981; }
|
|
121
|
+
.pentacad-panel .tag { font-size: 10px; opacity: 0.7; float: right; }
|
|
122
|
+
.pentacad-panel .section { margin-bottom: 16px; }
|
|
123
|
+
.pentacad-panel .section-title { font-size: 10px; color: #94a3b8; letter-spacing: 2px; margin-bottom: 6px; text-transform: uppercase; }
|
|
124
|
+
.pentacad-panel .note { color: #94a3b8; font-size: 11px; line-height: 1.5; }
|
|
125
|
+
.pentacad-panel .phase-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 10px; background: rgba(245,158,11,0.2); color: #f59e0b; font-weight: 600; }
|
|
126
|
+
</style>
|
|
127
|
+
|
|
128
|
+
<h3>PENTACAD <span class="phase-badge">v${VERSION} · Phase 0</span></h3>
|
|
129
|
+
|
|
130
|
+
<div class="section">
|
|
131
|
+
<div class="section-title">Machine</div>
|
|
132
|
+
<div class="machine-picker">
|
|
133
|
+
${SUPPORTED_MACHINES.map(m => `
|
|
134
|
+
<button data-machine="${m.id}">
|
|
135
|
+
${m.name}
|
|
136
|
+
<span class="tag">${m.status}</span>
|
|
137
|
+
</button>
|
|
138
|
+
`).join('')}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div class="section">
|
|
143
|
+
<div class="section-title">Workflow</div>
|
|
144
|
+
<div class="note">
|
|
145
|
+
1. Load a machine (above)<br />
|
|
146
|
+
2. Open or import a workpiece<br />
|
|
147
|
+
3. Define 3+2 setups<br />
|
|
148
|
+
4. Generate toolpaths<br />
|
|
149
|
+
5. Simulate<br />
|
|
150
|
+
6. Stream to machine
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div class="section">
|
|
155
|
+
<div class="section-title">Status</div>
|
|
156
|
+
<div class="note">
|
|
157
|
+
Machine: <span id="pc-machine-status">none</span><br />
|
|
158
|
+
Bridge: <span id="pc-bridge-status">disconnected</span>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
root.querySelectorAll('.machine-picker button').forEach(btn => {
|
|
164
|
+
btn.addEventListener('click', async () => {
|
|
165
|
+
root.querySelectorAll('.machine-picker button').forEach(b => b.classList.remove('active'));
|
|
166
|
+
btn.classList.add('active');
|
|
167
|
+
try {
|
|
168
|
+
await loadMachine(btn.dataset.machine);
|
|
169
|
+
const statusEl = root.querySelector('#pc-machine-status');
|
|
170
|
+
if (statusEl) statusEl.textContent = btn.dataset.machine;
|
|
171
|
+
} catch (e) {
|
|
172
|
+
alert(`Machine load failed: ${e.message}\n\nEnsure /machines/${btn.dataset.machine}/ files exist.`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
window.addEventListener('pentacad:bridge-status', (e) => {
|
|
178
|
+
const el = root.querySelector('#pc-bridge-status');
|
|
179
|
+
if (el) el.textContent = e.detail.status;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return root;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// EXECUTE (Agent API hook — routes to sub-modules)
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
function execute(request) {
|
|
190
|
+
const { method, params } = request || {};
|
|
191
|
+
if (!method) return { error: 'missing_method' };
|
|
192
|
+
|
|
193
|
+
const [ns, fn] = method.split('.');
|
|
194
|
+
switch (ns) {
|
|
195
|
+
case 'machine':
|
|
196
|
+
if (fn === 'load') return loadMachine(params.id);
|
|
197
|
+
if (fn === 'current') return state.machine;
|
|
198
|
+
break;
|
|
199
|
+
case 'cam':
|
|
200
|
+
return window.CycleCAD.PentacadCAM?.execute?.(request) ?? { error: 'cam_not_loaded' };
|
|
201
|
+
case 'sim':
|
|
202
|
+
return window.CycleCAD.PentacadSim?.execute?.(request) ?? { error: 'sim_not_loaded' };
|
|
203
|
+
case 'bridge':
|
|
204
|
+
return window.CycleCAD.PentacadBridge?.execute?.(request) ?? { error: 'bridge_not_loaded' };
|
|
205
|
+
}
|
|
206
|
+
return { error: 'unknown_method', method };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// PUBLIC API
|
|
211
|
+
// ============================================================================
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
version: VERSION,
|
|
215
|
+
MODULE_NAME,
|
|
216
|
+
init,
|
|
217
|
+
getUI,
|
|
218
|
+
execute,
|
|
219
|
+
loadMachine,
|
|
220
|
+
getState: () => ({ ...state }),
|
|
221
|
+
SUPPORTED_MACHINES,
|
|
222
|
+
};
|
|
223
|
+
})();
|
|
224
|
+
|
|
225
|
+
// Auto-init when the DOM is ready — but only if pentacad.html or main app
|
|
226
|
+
// signals they want it. Other pages can skip by setting window.CycleCAD.SkipPentacadInit.
|
|
227
|
+
if (typeof document !== 'undefined' && !window.CycleCAD.SkipPentacadInit) {
|
|
228
|
+
if (document.readyState === 'loading') {
|
|
229
|
+
document.addEventListener('DOMContentLoaded', () => window.CycleCAD.Pentacad.init());
|
|
230
|
+
} else {
|
|
231
|
+
window.CycleCAD.Pentacad.init();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
|
7
|
+
<title>Pentacad — cycleCAD Suite</title>
|
|
8
|
+
<meta name="description" content="Pentacad — browser-based CAM, 5-axis simulator, and machine control for the Pentamachine V2 line. Part of the cycleCAD Suite." />
|
|
9
|
+
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
--bg: #0f172a;
|
|
13
|
+
--panel: #1e293b;
|
|
14
|
+
--panel-2: #334155;
|
|
15
|
+
--text: #f1f5f9;
|
|
16
|
+
--muted: #94a3b8;
|
|
17
|
+
--border: #334155;
|
|
18
|
+
--accent: #10b981;
|
|
19
|
+
--accent-2: #0ea5e9;
|
|
20
|
+
--warn: #f59e0b;
|
|
21
|
+
--danger: #ef4444;
|
|
22
|
+
}
|
|
23
|
+
* { box-sizing: border-box; }
|
|
24
|
+
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden;
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
26
|
+
background: var(--bg); color: var(--text); font-size: 14px; }
|
|
27
|
+
|
|
28
|
+
#app {
|
|
29
|
+
display: grid;
|
|
30
|
+
grid-template-rows: 48px 1fr 28px;
|
|
31
|
+
grid-template-columns: 320px 1fr 340px;
|
|
32
|
+
grid-template-areas:
|
|
33
|
+
"header header header"
|
|
34
|
+
"left main right"
|
|
35
|
+
"footer footer footer";
|
|
36
|
+
height: 100vh;
|
|
37
|
+
}
|
|
38
|
+
header {
|
|
39
|
+
grid-area: header; background: var(--panel);
|
|
40
|
+
border-bottom: 1px solid var(--border);
|
|
41
|
+
display: flex; align-items: center; padding: 0 16px; gap: 12px;
|
|
42
|
+
}
|
|
43
|
+
.suite-mark {
|
|
44
|
+
font-size: 10px; color: var(--muted); letter-spacing: 3px;
|
|
45
|
+
border-right: 1px solid var(--border); padding-right: 12px;
|
|
46
|
+
}
|
|
47
|
+
.suite-mark a { color: inherit; text-decoration: none; }
|
|
48
|
+
.brand { font-weight: 700; letter-spacing: 3px; color: var(--accent); font-size: 14px; }
|
|
49
|
+
.tagline { color: var(--muted); font-size: 12px; margin-left: 4px; }
|
|
50
|
+
nav { margin-left: 24px; display: flex; gap: 4px; }
|
|
51
|
+
nav button { background: transparent; color: var(--muted);
|
|
52
|
+
border: 1px solid transparent; padding: 6px 12px; border-radius: 4px;
|
|
53
|
+
cursor: pointer; font-size: 13px; }
|
|
54
|
+
nav button.active { background: var(--panel-2); color: var(--text); border-color: var(--border); }
|
|
55
|
+
nav button:hover { color: var(--text); }
|
|
56
|
+
|
|
57
|
+
#left-panel { grid-area: left; background: var(--panel); border-right: 1px solid var(--border); overflow-y: auto; padding: 0; }
|
|
58
|
+
#viewport { grid-area: main; position: relative; background: radial-gradient(ellipse at center, #1e293b 0%, #0f172a 70%); }
|
|
59
|
+
#viewport canvas { display: block; width: 100%; height: 100%; }
|
|
60
|
+
#right-panel { grid-area: right; background: var(--panel); border-left: 1px solid var(--border); overflow-y: auto; padding: 12px; }
|
|
61
|
+
footer { grid-area: footer; background: var(--panel); border-top: 1px solid var(--border);
|
|
62
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
63
|
+
padding: 0 16px; font-size: 11px; color: var(--muted); }
|
|
64
|
+
|
|
65
|
+
.phase-banner {
|
|
66
|
+
background: rgba(245,158,11,0.12);
|
|
67
|
+
border: 1px solid rgba(245,158,11,0.45);
|
|
68
|
+
color: var(--warn); padding: 10px 12px; border-radius: 6px;
|
|
69
|
+
font-size: 12px; line-height: 1.5; margin: 12px;
|
|
70
|
+
}
|
|
71
|
+
.section-title {
|
|
72
|
+
font-size: 10px; font-weight: 700; letter-spacing: 2px;
|
|
73
|
+
color: var(--muted); margin: 16px 12px 8px 12px;
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
}
|
|
76
|
+
#bridge-status {
|
|
77
|
+
padding: 4px 10px; border-radius: 4px; font-size: 11px;
|
|
78
|
+
background: rgba(239,68,68,0.18); color: var(--danger);
|
|
79
|
+
}
|
|
80
|
+
#bridge-status.connected { background: rgba(16,185,129,0.18); color: var(--accent); }
|
|
81
|
+
.stat { padding: 8px 12px; color: var(--muted); font-size: 11px; }
|
|
82
|
+
.stat strong { color: var(--text); font-weight: 600; }
|
|
83
|
+
</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<div id="app">
|
|
87
|
+
<header>
|
|
88
|
+
<span class="suite-mark"><a href="/">cycleCAD SUITE</a></span>
|
|
89
|
+
<span class="brand">PENTACAD</span>
|
|
90
|
+
<span class="tagline">v0.1.0 · Phase 0 scaffold</span>
|
|
91
|
+
<nav id="workspace-tabs">
|
|
92
|
+
<button data-ws="machine" class="active">Machine</button>
|
|
93
|
+
<button data-ws="design">Design</button>
|
|
94
|
+
<button data-ws="cam">CAM</button>
|
|
95
|
+
<button data-ws="simulate">Simulate</button>
|
|
96
|
+
<button data-ws="control">Control</button>
|
|
97
|
+
</nav>
|
|
98
|
+
<span style="flex:1"></span>
|
|
99
|
+
<span id="bridge-status" title="Controller bridge status">Bridge: disconnected</span>
|
|
100
|
+
</header>
|
|
101
|
+
|
|
102
|
+
<aside id="left-panel">
|
|
103
|
+
<div class="phase-banner">
|
|
104
|
+
<strong>Phase 0 scaffold.</strong><br />
|
|
105
|
+
UI shell is live. Machine selector works once machine JSON files exist under <code>/machines/</code>.
|
|
106
|
+
</div>
|
|
107
|
+
<div id="pentacad-panel-mount"><!-- window.CycleCAD.Pentacad.getUI() renders here --></div>
|
|
108
|
+
</aside>
|
|
109
|
+
|
|
110
|
+
<main id="viewport"><!-- Three.js canvas injected --></main>
|
|
111
|
+
|
|
112
|
+
<aside id="right-panel">
|
|
113
|
+
<h3 class="section-title">Workspace</h3>
|
|
114
|
+
<div id="workspace-content">
|
|
115
|
+
<div id="ws-machine" class="stat">
|
|
116
|
+
Pick a machine from the left panel. The digital twin renders in the viewport once the machine definition loads.
|
|
117
|
+
</div>
|
|
118
|
+
<div id="ws-design" class="stat" hidden>
|
|
119
|
+
<strong>Design</strong> — delegates to cycleCAD modelling (sketch, solid, assembly). Workpiece becomes the input for CAM.
|
|
120
|
+
</div>
|
|
121
|
+
<div id="ws-cam" class="stat" hidden>
|
|
122
|
+
<strong>CAM</strong> — Phase 2. 3+2 setup manager, 12 strategies, Pentamachine post-processor. See <code>pentacad-cam.js</code>.
|
|
123
|
+
</div>
|
|
124
|
+
<div id="ws-simulate" class="stat" hidden>
|
|
125
|
+
<strong>Simulate</strong> — Phase 1. G-code replay with forward kinematics, collision check, material removal. See <code>pentacad-sim.js</code>.
|
|
126
|
+
</div>
|
|
127
|
+
<div id="ws-control" class="stat" hidden>
|
|
128
|
+
<strong>Control</strong> — Phase 3. Stream G-code to Kinetic Control via WebSocket bridge, live DRO readback. See <code>pentacad-bridge.js</code>.
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<h3 class="section-title">Agent API</h3>
|
|
133
|
+
<div class="stat">
|
|
134
|
+
<code style="color:var(--accent-2);font-size:11px">window.CycleCAD.Pentacad.execute({...})</code><br /><br />
|
|
135
|
+
Namespaces: <strong>machine.*</strong>, <strong>cam.*</strong>, <strong>sim.*</strong>, <strong>bridge.*</strong>
|
|
136
|
+
</div>
|
|
137
|
+
</aside>
|
|
138
|
+
|
|
139
|
+
<footer>
|
|
140
|
+
<span>Pentacad · part of the cycleCAD Suite · AGPL-3.0</span>
|
|
141
|
+
<span><span id="fps">60</span> fps · <span id="tri-count">0</span> tris</span>
|
|
142
|
+
</footer>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- Three.js -->
|
|
146
|
+
<script type="importmap">
|
|
147
|
+
{
|
|
148
|
+
"imports": {
|
|
149
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<!-- Pentacad sub-modules — load before the coordinator -->
|
|
155
|
+
<script src="./js/modules/pentacad-cam.js"></script>
|
|
156
|
+
<script src="./js/modules/pentacad-sim.js"></script>
|
|
157
|
+
<script src="./js/modules/pentacad-bridge.js"></script>
|
|
158
|
+
<script src="./js/modules/pentacad.js"></script>
|
|
159
|
+
|
|
160
|
+
<script type="module">
|
|
161
|
+
import * as THREE from 'three';
|
|
162
|
+
|
|
163
|
+
const viewportEl = document.getElementById('viewport');
|
|
164
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
165
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
166
|
+
renderer.setSize(viewportEl.clientWidth, viewportEl.clientHeight);
|
|
167
|
+
renderer.setClearColor(0x0f172a, 1);
|
|
168
|
+
viewportEl.appendChild(renderer.domElement);
|
|
169
|
+
|
|
170
|
+
const scene = new THREE.Scene();
|
|
171
|
+
const camera = new THREE.PerspectiveCamera(40, viewportEl.clientWidth / viewportEl.clientHeight, 0.1, 10000);
|
|
172
|
+
camera.position.set(18, 12, 18);
|
|
173
|
+
camera.lookAt(0, 4, 0);
|
|
174
|
+
|
|
175
|
+
scene.add(new THREE.GridHelper(20, 20, 0x475569, 0x1e293b));
|
|
176
|
+
const axes = new THREE.AxesHelper(3); axes.position.y = 0.01; scene.add(axes);
|
|
177
|
+
scene.add(new THREE.HemisphereLight(0xeef2ff, 0x334155, 0.9));
|
|
178
|
+
const key = new THREE.DirectionalLight(0xffffff, 0.7); key.position.set(10, 15, 8); scene.add(key);
|
|
179
|
+
|
|
180
|
+
// Placeholder for the machine
|
|
181
|
+
const placeholder = new THREE.Group();
|
|
182
|
+
placeholder.add(new THREE.Mesh(
|
|
183
|
+
new THREE.BoxGeometry(6, 0.2, 4),
|
|
184
|
+
new THREE.MeshStandardMaterial({ color: 0x1e293b, metalness: 0.4, roughness: 0.7 })
|
|
185
|
+
));
|
|
186
|
+
placeholder.children[0].position.y = 0.1;
|
|
187
|
+
scene.add(placeholder);
|
|
188
|
+
|
|
189
|
+
// Mount the Pentacad panel into the left side
|
|
190
|
+
const mount = document.getElementById('pentacad-panel-mount');
|
|
191
|
+
if (window.CycleCAD?.Pentacad?.getUI) mount.appendChild(window.CycleCAD.Pentacad.getUI());
|
|
192
|
+
|
|
193
|
+
// Workspace tab wiring
|
|
194
|
+
const tabs = document.querySelectorAll('#workspace-tabs button');
|
|
195
|
+
const panels = {
|
|
196
|
+
machine: document.getElementById('ws-machine'),
|
|
197
|
+
design: document.getElementById('ws-design'),
|
|
198
|
+
cam: document.getElementById('ws-cam'),
|
|
199
|
+
simulate: document.getElementById('ws-simulate'),
|
|
200
|
+
control: document.getElementById('ws-control'),
|
|
201
|
+
};
|
|
202
|
+
tabs.forEach(btn => btn.addEventListener('click', () => {
|
|
203
|
+
tabs.forEach(b => b.classList.remove('active'));
|
|
204
|
+
btn.classList.add('active');
|
|
205
|
+
Object.values(panels).forEach(p => p.hidden = true);
|
|
206
|
+
panels[btn.dataset.ws].hidden = false;
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
// Expose for debugging
|
|
210
|
+
window._scene = scene; window._camera = camera; window._renderer = renderer;
|
|
211
|
+
|
|
212
|
+
// FPS + tri counter
|
|
213
|
+
let frames = 0, lastFps = performance.now();
|
|
214
|
+
const fpsEl = document.getElementById('fps');
|
|
215
|
+
const triEl = document.getElementById('tri-count');
|
|
216
|
+
function animate() {
|
|
217
|
+
requestAnimationFrame(animate);
|
|
218
|
+
renderer.render(scene, camera);
|
|
219
|
+
frames++;
|
|
220
|
+
const now = performance.now();
|
|
221
|
+
if (now - lastFps > 500) {
|
|
222
|
+
fpsEl.textContent = Math.round((frames * 1000) / (now - lastFps));
|
|
223
|
+
triEl.textContent = renderer.info.render.triangles.toLocaleString();
|
|
224
|
+
frames = 0; lastFps = now;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
animate();
|
|
228
|
+
|
|
229
|
+
window.addEventListener('resize', () => {
|
|
230
|
+
const w = viewportEl.clientWidth, h = viewportEl.clientHeight;
|
|
231
|
+
renderer.setSize(w, h);
|
|
232
|
+
camera.aspect = w / h;
|
|
233
|
+
camera.updateProjectionMatrix();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
console.log('%cPentacad · cycleCAD Suite', 'color:#10b981;font-weight:bold;font-size:14px');
|
|
237
|
+
console.log('Available: window.CycleCAD.Pentacad.{getUI, execute, loadMachine, SUPPORTED_MACHINES}');
|
|
238
|
+
</script>
|
|
239
|
+
</body>
|
|
240
|
+
</html>
|