cyclecad 0.1.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/CNAME +1 -0
- package/app/docs/api-reference.html +1436 -0
- package/app/docs/examples.html +803 -0
- package/app/docs/getting-started.html +1620 -0
- package/app/duo-project-browser.html +1321 -0
- package/app/duo-rebuild-guide.html +861 -0
- package/app/index.html +1635 -0
- package/app/js/ai-chat.js +992 -0
- package/app/js/app.js +724 -0
- package/app/js/export.js +658 -0
- package/app/js/inventor-parser.js +1138 -0
- package/app/js/operations.js +689 -0
- package/app/js/params.js +523 -0
- package/app/js/reverse-engineer.js +1275 -0
- package/app/js/shortcuts.js +350 -0
- package/app/js/sketch.js +899 -0
- package/app/js/tree.js +479 -0
- package/app/js/viewport.js +643 -0
- package/app/samples/Leistenbuerstenblech.ipt +0 -0
- package/app/samples/Rahmen_Seite.iam +0 -0
- package/app/samples/TraegerHoehe1.ipt +0 -0
- package/index.html +1226 -0
- package/package.json +33 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard Shortcuts Module for cycleCAD
|
|
3
|
+
* Registers and manages all keyboard shortcuts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let shortcutsPanel = null;
|
|
7
|
+
let shortcutsVisible = false;
|
|
8
|
+
|
|
9
|
+
const SHORTCUT_MAP = {
|
|
10
|
+
// Sketch tools
|
|
11
|
+
's': { action: 'newSketch', label: 'New Sketch', category: 'Sketch' },
|
|
12
|
+
'l': { action: 'line', label: 'Line Tool', category: 'Sketch Tools' },
|
|
13
|
+
'r': { action: 'rect', label: 'Rectangle Tool', category: 'Sketch Tools' },
|
|
14
|
+
'c': { action: 'circle', label: 'Circle Tool', category: 'Sketch Tools' },
|
|
15
|
+
'a': { action: 'arc', label: 'Arc Tool', category: 'Sketch Tools' },
|
|
16
|
+
'd': { action: 'dimension', label: 'Dimension Tool', category: 'Sketch Tools' },
|
|
17
|
+
|
|
18
|
+
// 3D Operations
|
|
19
|
+
'e': { action: 'extrude', label: 'Extrude', category: '3D Operations' },
|
|
20
|
+
'v': { action: 'revolve', label: 'Revolve', category: '3D Operations' },
|
|
21
|
+
'f': { action: 'fillet', label: 'Fillet', category: '3D Operations' },
|
|
22
|
+
'shift+f': { action: 'chamfer', label: 'Chamfer', category: '3D Operations' },
|
|
23
|
+
|
|
24
|
+
// Boolean operations
|
|
25
|
+
'ctrl+u': { action: 'union', label: 'Union', category: 'Boolean' },
|
|
26
|
+
'ctrl+minus': { action: 'cut', label: 'Cut', category: 'Boolean' },
|
|
27
|
+
|
|
28
|
+
// Edit
|
|
29
|
+
'ctrl+z': { action: 'undo', label: 'Undo', category: 'Edit' },
|
|
30
|
+
'ctrl+y': { action: 'redo', label: 'Redo', category: 'Edit' },
|
|
31
|
+
'ctrl+shift+z': { action: 'redo', label: 'Redo', category: 'Edit' },
|
|
32
|
+
'delete': { action: 'delete', label: 'Delete Selected', category: 'Edit' },
|
|
33
|
+
'backspace': { action: 'delete', label: 'Delete Selected', category: 'Edit' },
|
|
34
|
+
'escape': { action: 'escape', label: 'Cancel / Deselect', category: 'Edit' },
|
|
35
|
+
'enter': { action: 'enter', label: 'Confirm', category: 'Edit' },
|
|
36
|
+
|
|
37
|
+
// Views
|
|
38
|
+
'1': { action: 'viewFront', label: 'Front View', category: 'Views' },
|
|
39
|
+
'2': { action: 'viewBack', label: 'Back View', category: 'Views' },
|
|
40
|
+
'3': { action: 'viewRight', label: 'Right View', category: 'Views' },
|
|
41
|
+
'4': { action: 'viewLeft', label: 'Left View', category: 'Views' },
|
|
42
|
+
'5': { action: 'viewTop', label: 'Top View', category: 'Views' },
|
|
43
|
+
'6': { action: 'viewBottom', label: 'Bottom View', category: 'Views' },
|
|
44
|
+
'7': { action: 'viewIso', label: 'Isometric View', category: 'Views' },
|
|
45
|
+
|
|
46
|
+
// Display
|
|
47
|
+
'g': { action: 'toggleGrid', label: 'Toggle Grid', category: 'Display' },
|
|
48
|
+
'w': { action: 'toggleWireframe', label: 'Toggle Wireframe', category: 'Display' },
|
|
49
|
+
'shift+f': { action: 'fitAll', label: 'Fit All', category: 'Display' },
|
|
50
|
+
|
|
51
|
+
// Export & Save
|
|
52
|
+
'ctrl+s': { action: 'save', label: 'Save (Export JSON)', category: 'File' },
|
|
53
|
+
'ctrl+shift+e': { action: 'exportSTL', label: 'Export STL', category: 'File' },
|
|
54
|
+
|
|
55
|
+
// Help
|
|
56
|
+
'?': { action: 'showHelp', label: 'Show Shortcuts', category: 'Help' },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize keyboard shortcuts
|
|
61
|
+
* @param {Object} handlers - Map of action names to handler functions
|
|
62
|
+
*/
|
|
63
|
+
export function initShortcuts(handlers) {
|
|
64
|
+
document.addEventListener('keydown', (e) => {
|
|
65
|
+
// Skip if focus is on input/textarea/select
|
|
66
|
+
if (isInputFocused()) return;
|
|
67
|
+
|
|
68
|
+
const key = getKeyCombo(e);
|
|
69
|
+
const shortcut = SHORTCUT_MAP[key.toLowerCase()];
|
|
70
|
+
|
|
71
|
+
if (shortcut && handlers[shortcut.action]) {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
handlers[shortcut.action]();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get keyboard combination string (e.g., "ctrl+z", "shift+a")
|
|
80
|
+
* @param {KeyboardEvent} e
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function getKeyCombo(e) {
|
|
84
|
+
const parts = [];
|
|
85
|
+
if (e.ctrlKey) parts.push('ctrl');
|
|
86
|
+
if (e.shiftKey) parts.push('shift');
|
|
87
|
+
if (e.altKey) parts.push('alt');
|
|
88
|
+
|
|
89
|
+
// Get the actual key
|
|
90
|
+
let key = e.key.toLowerCase();
|
|
91
|
+
if (key === 'control' || key === 'shift' || key === 'alt' || key === 'meta') {
|
|
92
|
+
return parts.join('+'); // Modifier key only, not a valid shortcut
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Special key names
|
|
96
|
+
const specialKeys = {
|
|
97
|
+
'arrowup': 'up',
|
|
98
|
+
'arrowdown': 'down',
|
|
99
|
+
'arrowleft': 'left',
|
|
100
|
+
'arrowright': 'right',
|
|
101
|
+
' ': 'space',
|
|
102
|
+
'enter': 'enter',
|
|
103
|
+
'escape': 'escape',
|
|
104
|
+
'delete': 'delete',
|
|
105
|
+
'backspace': 'backspace',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
key = specialKeys[key] || key;
|
|
109
|
+
parts.push(key);
|
|
110
|
+
|
|
111
|
+
return parts.join('+');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if an input/textarea/select is currently focused
|
|
116
|
+
* @returns {boolean}
|
|
117
|
+
*/
|
|
118
|
+
function isInputFocused() {
|
|
119
|
+
const activeEl = document.activeElement;
|
|
120
|
+
return (
|
|
121
|
+
activeEl &&
|
|
122
|
+
(activeEl.tagName === 'INPUT' ||
|
|
123
|
+
activeEl.tagName === 'TEXTAREA' ||
|
|
124
|
+
activeEl.tagName === 'SELECT' ||
|
|
125
|
+
activeEl.contentEditable === 'true')
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Show the shortcuts help panel
|
|
131
|
+
*/
|
|
132
|
+
export function showShortcutsPanel() {
|
|
133
|
+
if (shortcutsVisible && shortcutsPanel) {
|
|
134
|
+
shortcutsPanel.style.display = 'none';
|
|
135
|
+
shortcutsVisible = false;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Create panel if it doesn't exist
|
|
140
|
+
if (!shortcutsPanel) {
|
|
141
|
+
shortcutsPanel = createShortcutsPanel();
|
|
142
|
+
document.body.appendChild(shortcutsPanel);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
shortcutsPanel.style.display = 'flex';
|
|
146
|
+
shortcutsVisible = true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create the shortcuts panel DOM element
|
|
151
|
+
* @returns {HTMLElement}
|
|
152
|
+
*/
|
|
153
|
+
function createShortcutsPanel() {
|
|
154
|
+
const panel = document.createElement('div');
|
|
155
|
+
panel.id = 'shortcuts-panel';
|
|
156
|
+
panel.style.cssText = `
|
|
157
|
+
position: fixed;
|
|
158
|
+
top: 50%;
|
|
159
|
+
left: 50%;
|
|
160
|
+
transform: translate(-50%, -50%);
|
|
161
|
+
width: 90%;
|
|
162
|
+
max-width: 800px;
|
|
163
|
+
max-height: 80vh;
|
|
164
|
+
background: #1a1a1a;
|
|
165
|
+
border: 2px solid #444;
|
|
166
|
+
border-radius: 8px;
|
|
167
|
+
padding: 24px;
|
|
168
|
+
z-index: 10000;
|
|
169
|
+
display: flex;
|
|
170
|
+
flex-direction: column;
|
|
171
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
// Header
|
|
176
|
+
const header = document.createElement('div');
|
|
177
|
+
header.style.cssText = `
|
|
178
|
+
display: flex;
|
|
179
|
+
justify-content: space-between;
|
|
180
|
+
align-items: center;
|
|
181
|
+
margin-bottom: 16px;
|
|
182
|
+
padding-bottom: 12px;
|
|
183
|
+
border-bottom: 1px solid #444;
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
const title = document.createElement('h2');
|
|
187
|
+
title.textContent = 'Keyboard Shortcuts';
|
|
188
|
+
title.style.cssText = `
|
|
189
|
+
margin: 0;
|
|
190
|
+
color: #fff;
|
|
191
|
+
font-size: 18px;
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
const closeBtn = document.createElement('button');
|
|
195
|
+
closeBtn.textContent = '✕';
|
|
196
|
+
closeBtn.style.cssText = `
|
|
197
|
+
background: none;
|
|
198
|
+
border: none;
|
|
199
|
+
color: #aaa;
|
|
200
|
+
font-size: 24px;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
padding: 0;
|
|
203
|
+
width: 24px;
|
|
204
|
+
height: 24px;
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
`;
|
|
209
|
+
closeBtn.onclick = () => showShortcutsPanel();
|
|
210
|
+
closeBtn.onmouseover = () => (closeBtn.style.color = '#fff');
|
|
211
|
+
closeBtn.onmouseout = () => (closeBtn.style.color = '#aaa');
|
|
212
|
+
|
|
213
|
+
header.appendChild(title);
|
|
214
|
+
header.appendChild(closeBtn);
|
|
215
|
+
|
|
216
|
+
// Content
|
|
217
|
+
const content = document.createElement('div');
|
|
218
|
+
content.style.cssText = `
|
|
219
|
+
overflow-y: auto;
|
|
220
|
+
flex: 1;
|
|
221
|
+
color: #ddd;
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
// Group shortcuts by category
|
|
225
|
+
const categories = {};
|
|
226
|
+
Object.entries(SHORTCUT_MAP).forEach(([key, shortcut]) => {
|
|
227
|
+
if (!categories[shortcut.category]) {
|
|
228
|
+
categories[shortcut.category] = [];
|
|
229
|
+
}
|
|
230
|
+
categories[shortcut.category].push({ key, ...shortcut });
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Render categories
|
|
234
|
+
Object.entries(categories).forEach(([category, shortcuts]) => {
|
|
235
|
+
const section = document.createElement('div');
|
|
236
|
+
section.style.cssText = `
|
|
237
|
+
margin-bottom: 20px;
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
const catTitle = document.createElement('h3');
|
|
241
|
+
catTitle.textContent = category;
|
|
242
|
+
catTitle.style.cssText = `
|
|
243
|
+
margin: 0 0 8px 0;
|
|
244
|
+
font-size: 12px;
|
|
245
|
+
color: #999;
|
|
246
|
+
text-transform: uppercase;
|
|
247
|
+
letter-spacing: 1px;
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
const shortcuts_list = document.createElement('div');
|
|
251
|
+
shortcuts_list.style.cssText = `
|
|
252
|
+
display: grid;
|
|
253
|
+
grid-template-columns: 1fr 1fr;
|
|
254
|
+
gap: 8px;
|
|
255
|
+
`;
|
|
256
|
+
|
|
257
|
+
shortcuts.forEach(({ key, label }) => {
|
|
258
|
+
const row = document.createElement('div');
|
|
259
|
+
row.style.cssText = `
|
|
260
|
+
display: flex;
|
|
261
|
+
justify-content: space-between;
|
|
262
|
+
align-items: center;
|
|
263
|
+
padding: 6px 8px;
|
|
264
|
+
border-radius: 4px;
|
|
265
|
+
background: rgba(255,255,255,0.05);
|
|
266
|
+
font-size: 12px;
|
|
267
|
+
`;
|
|
268
|
+
|
|
269
|
+
const labelEl = document.createElement('span');
|
|
270
|
+
labelEl.textContent = label;
|
|
271
|
+
labelEl.style.color = '#ddd';
|
|
272
|
+
|
|
273
|
+
const keyEl = document.createElement('kbd');
|
|
274
|
+
keyEl.textContent = key.toUpperCase();
|
|
275
|
+
keyEl.style.cssText = `
|
|
276
|
+
background: rgba(255,255,255,0.1);
|
|
277
|
+
padding: 2px 6px;
|
|
278
|
+
border-radius: 3px;
|
|
279
|
+
font-family: monospace;
|
|
280
|
+
font-size: 10px;
|
|
281
|
+
color: #4a9eff;
|
|
282
|
+
border: 1px solid rgba(74,158,255,0.3);
|
|
283
|
+
`;
|
|
284
|
+
|
|
285
|
+
row.appendChild(labelEl);
|
|
286
|
+
row.appendChild(keyEl);
|
|
287
|
+
shortcuts_list.appendChild(row);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
section.appendChild(catTitle);
|
|
291
|
+
section.appendChild(shortcuts_list);
|
|
292
|
+
content.appendChild(section);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Footer
|
|
296
|
+
const footer = document.createElement('div');
|
|
297
|
+
footer.style.cssText = `
|
|
298
|
+
margin-top: 16px;
|
|
299
|
+
padding-top: 12px;
|
|
300
|
+
border-top: 1px solid #444;
|
|
301
|
+
font-size: 11px;
|
|
302
|
+
color: #666;
|
|
303
|
+
`;
|
|
304
|
+
footer.textContent = 'Press ? to toggle this panel';
|
|
305
|
+
|
|
306
|
+
panel.appendChild(header);
|
|
307
|
+
panel.appendChild(content);
|
|
308
|
+
panel.appendChild(footer);
|
|
309
|
+
|
|
310
|
+
// Close on Escape
|
|
311
|
+
const closeOnEscape = (e) => {
|
|
312
|
+
if (e.key === 'Escape' && shortcutsVisible) {
|
|
313
|
+
showShortcutsPanel();
|
|
314
|
+
document.removeEventListener('keydown', closeOnEscape);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
document.addEventListener('keydown', closeOnEscape);
|
|
318
|
+
|
|
319
|
+
return panel;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get list of all shortcuts for help/documentation
|
|
324
|
+
* @returns {Array}
|
|
325
|
+
*/
|
|
326
|
+
export function getShortcutsList() {
|
|
327
|
+
return Object.entries(SHORTCUT_MAP).map(([key, shortcut]) => ({
|
|
328
|
+
key,
|
|
329
|
+
...shortcut,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Check if a key combination is valid
|
|
335
|
+
* @param {string} key - Key combination (e.g., "ctrl+z")
|
|
336
|
+
* @returns {boolean}
|
|
337
|
+
*/
|
|
338
|
+
export function isValidShortcut(key) {
|
|
339
|
+
return key.toLowerCase() in SHORTCUT_MAP;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Get the handler function name for a key combination
|
|
344
|
+
* @param {string} key
|
|
345
|
+
* @returns {string|null}
|
|
346
|
+
*/
|
|
347
|
+
export function getShortcutAction(key) {
|
|
348
|
+
const shortcut = SHORTCUT_MAP[key.toLowerCase()];
|
|
349
|
+
return shortcut ? shortcut.action : null;
|
|
350
|
+
}
|