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.
@@ -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>