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
package/app/js/params.js
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* params.js - Parameter editor panel for cycleCAD
|
|
3
|
+
* Manages feature parameters, materials, and property display
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const MATERIALS = {
|
|
7
|
+
Steel: {
|
|
8
|
+
density: 7.85, // g/cm³
|
|
9
|
+
color: 0x8899aa,
|
|
10
|
+
label: 'Steel',
|
|
11
|
+
},
|
|
12
|
+
Aluminum: {
|
|
13
|
+
density: 2.7,
|
|
14
|
+
color: 0xb0b8c4,
|
|
15
|
+
label: 'Aluminum',
|
|
16
|
+
},
|
|
17
|
+
ABS: {
|
|
18
|
+
density: 1.05,
|
|
19
|
+
color: 0x2a2a2e,
|
|
20
|
+
label: 'ABS',
|
|
21
|
+
},
|
|
22
|
+
Brass: {
|
|
23
|
+
density: 8.5,
|
|
24
|
+
color: 0xc4a54a,
|
|
25
|
+
label: 'Brass',
|
|
26
|
+
},
|
|
27
|
+
Titanium: {
|
|
28
|
+
density: 4.5,
|
|
29
|
+
color: 0x8a8a90,
|
|
30
|
+
label: 'Titanium',
|
|
31
|
+
},
|
|
32
|
+
Nylon: {
|
|
33
|
+
density: 1.14,
|
|
34
|
+
color: 0xe8e0d0,
|
|
35
|
+
label: 'Nylon',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let paramsState = {
|
|
40
|
+
containerEl: null,
|
|
41
|
+
currentFeature: null,
|
|
42
|
+
onParamChangeCallback: null,
|
|
43
|
+
onMaterialChangeCallback: null,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initialize the parameters panel
|
|
48
|
+
* @param {HTMLElement} containerEl - Container for the panel
|
|
49
|
+
*/
|
|
50
|
+
export function initParams(containerEl) {
|
|
51
|
+
paramsState.containerEl = containerEl;
|
|
52
|
+
paramsState.currentFeature = null;
|
|
53
|
+
|
|
54
|
+
// Create panel structure
|
|
55
|
+
containerEl.innerHTML = `
|
|
56
|
+
<div class="params-panel">
|
|
57
|
+
<div class="params-header">
|
|
58
|
+
<h3>Properties</h3>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="params-content" id="params-content">
|
|
61
|
+
<div class="params-empty">
|
|
62
|
+
<p>Select a feature to see parameters</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
// Add styles if not already present
|
|
69
|
+
if (!document.getElementById('params-styles')) {
|
|
70
|
+
const style = document.createElement('style');
|
|
71
|
+
style.id = 'params-styles';
|
|
72
|
+
style.textContent = `
|
|
73
|
+
.params-panel {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
height: 100%;
|
|
77
|
+
background: var(--surface, #1e1e1e);
|
|
78
|
+
color: var(--text, #e0e0e0);
|
|
79
|
+
border-left: 1px solid var(--border, #333);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.params-header {
|
|
83
|
+
padding: 16px;
|
|
84
|
+
border-bottom: 1px solid var(--border, #333);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.params-header h3 {
|
|
88
|
+
margin: 0;
|
|
89
|
+
font-size: 14px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
color: var(--text, #e0e0e0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.params-content {
|
|
95
|
+
flex: 1;
|
|
96
|
+
overflow-y: auto;
|
|
97
|
+
min-height: 0;
|
|
98
|
+
padding: 16px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.params-content::-webkit-scrollbar {
|
|
102
|
+
width: 8px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.params-content::-webkit-scrollbar-track {
|
|
106
|
+
background: transparent;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.params-content::-webkit-scrollbar-thumb {
|
|
110
|
+
background: var(--border, #333);
|
|
111
|
+
border-radius: 4px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.params-content::-webkit-scrollbar-thumb:hover {
|
|
115
|
+
background: var(--text2, #a0a0a0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.params-empty {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
justify-content: center;
|
|
122
|
+
height: 100%;
|
|
123
|
+
color: var(--text2, #a0a0a0);
|
|
124
|
+
font-size: 13px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.params-group {
|
|
128
|
+
margin-bottom: 20px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.params-group-title {
|
|
132
|
+
font-size: 12px;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
color: var(--text2, #a0a0a0);
|
|
135
|
+
text-transform: uppercase;
|
|
136
|
+
margin-bottom: 12px;
|
|
137
|
+
letter-spacing: 0.5px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.param-row {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 8px;
|
|
144
|
+
margin-bottom: 12px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.param-label {
|
|
148
|
+
flex: 1;
|
|
149
|
+
font-size: 13px;
|
|
150
|
+
color: var(--text, #e0e0e0);
|
|
151
|
+
white-space: nowrap;
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
text-overflow: ellipsis;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.param-input-wrapper {
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 4px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.param-input {
|
|
163
|
+
width: 80px;
|
|
164
|
+
padding: 6px 8px;
|
|
165
|
+
background: var(--bg, #141414);
|
|
166
|
+
border: 1px solid var(--border, #333);
|
|
167
|
+
border-radius: 3px;
|
|
168
|
+
color: var(--text, #e0e0e0);
|
|
169
|
+
font-size: 12px;
|
|
170
|
+
font-family: monospace;
|
|
171
|
+
transition: border-color 0.15s, background 0.15s;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.param-input:focus {
|
|
175
|
+
outline: none;
|
|
176
|
+
border-color: var(--accent, #6496ff);
|
|
177
|
+
background: var(--bg, #1a1a1a);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.param-unit {
|
|
181
|
+
font-size: 12px;
|
|
182
|
+
color: var(--text2, #a0a0a0);
|
|
183
|
+
min-width: 24px;
|
|
184
|
+
text-align: right;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.material-section {
|
|
188
|
+
margin-bottom: 20px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.material-label {
|
|
192
|
+
font-size: 12px;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
color: var(--text2, #a0a0a0);
|
|
195
|
+
text-transform: uppercase;
|
|
196
|
+
margin-bottom: 8px;
|
|
197
|
+
letter-spacing: 0.5px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.material-select {
|
|
201
|
+
width: 100%;
|
|
202
|
+
padding: 8px 10px;
|
|
203
|
+
background: var(--bg, #141414);
|
|
204
|
+
border: 1px solid var(--border, #333);
|
|
205
|
+
border-radius: 3px;
|
|
206
|
+
color: var(--text, #e0e0e0);
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
transition: border-color 0.15s, background 0.15s;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.material-select:focus {
|
|
213
|
+
outline: none;
|
|
214
|
+
border-color: var(--accent, #6496ff);
|
|
215
|
+
background: var(--bg, #1a1a1a);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.material-select option {
|
|
219
|
+
background: var(--surface, #1e1e1e);
|
|
220
|
+
color: var(--text, #e0e0e0);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.material-info {
|
|
224
|
+
margin-top: 12px;
|
|
225
|
+
padding: 10px 12px;
|
|
226
|
+
background: rgba(100, 150, 255, 0.08);
|
|
227
|
+
border-left: 3px solid var(--accent, #6496ff);
|
|
228
|
+
border-radius: 2px;
|
|
229
|
+
font-size: 12px;
|
|
230
|
+
color: var(--text, #e0e0e0);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.material-info-row {
|
|
234
|
+
display: flex;
|
|
235
|
+
justify-content: space-between;
|
|
236
|
+
margin-bottom: 4px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.material-info-row:last-child {
|
|
240
|
+
margin-bottom: 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.material-info-label {
|
|
244
|
+
color: var(--text2, #a0a0a0);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.material-color-preview {
|
|
248
|
+
display: inline-block;
|
|
249
|
+
width: 16px;
|
|
250
|
+
height: 16px;
|
|
251
|
+
border-radius: 2px;
|
|
252
|
+
border: 1px solid var(--border, #333);
|
|
253
|
+
vertical-align: middle;
|
|
254
|
+
margin-right: 6px;
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
document.head.appendChild(style);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Show parameters for a feature
|
|
263
|
+
* @param {Object} feature - Feature object with params
|
|
264
|
+
*/
|
|
265
|
+
export function showParams(feature) {
|
|
266
|
+
if (!feature) {
|
|
267
|
+
clearParams();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
paramsState.currentFeature = feature;
|
|
272
|
+
const content = document.getElementById('params-content');
|
|
273
|
+
if (!content) return;
|
|
274
|
+
|
|
275
|
+
let html = '';
|
|
276
|
+
|
|
277
|
+
// Feature info
|
|
278
|
+
html += `
|
|
279
|
+
<div class="params-group">
|
|
280
|
+
<div class="params-group-title">Feature</div>
|
|
281
|
+
<div class="param-row">
|
|
282
|
+
<span class="param-label">Type:</span>
|
|
283
|
+
<span class="param-unit">${feature.type}</span>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="param-row">
|
|
286
|
+
<span class="param-label">Name:</span>
|
|
287
|
+
<span class="param-unit">${feature.name}</span>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
`;
|
|
291
|
+
|
|
292
|
+
// Parameters section
|
|
293
|
+
if (feature.params && Object.keys(feature.params).length > 0) {
|
|
294
|
+
html += `<div class="params-group">`;
|
|
295
|
+
html += `<div class="params-group-title">Parameters</div>`;
|
|
296
|
+
|
|
297
|
+
for (const [paramName, paramValue] of Object.entries(feature.params)) {
|
|
298
|
+
const displayName = paramName
|
|
299
|
+
.replace(/([A-Z])/g, ' $1')
|
|
300
|
+
.replace(/^./, (str) => str.toUpperCase())
|
|
301
|
+
.trim();
|
|
302
|
+
|
|
303
|
+
html += `
|
|
304
|
+
<div class="param-row">
|
|
305
|
+
<label class="param-label" for="param-${paramName}">${displayName}:</label>
|
|
306
|
+
<div class="param-input-wrapper">
|
|
307
|
+
<input
|
|
308
|
+
type="number"
|
|
309
|
+
id="param-${paramName}"
|
|
310
|
+
class="param-input"
|
|
311
|
+
data-param="${paramName}"
|
|
312
|
+
value="${typeof paramValue === 'number' ? paramValue.toFixed(2) : paramValue}"
|
|
313
|
+
step="0.1"
|
|
314
|
+
/>
|
|
315
|
+
<span class="param-unit">mm</span>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
html += `</div>`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Material section
|
|
325
|
+
html += `
|
|
326
|
+
<div class="material-section">
|
|
327
|
+
<div class="material-label">Material</div>
|
|
328
|
+
<select class="material-select" id="material-select">
|
|
329
|
+
`;
|
|
330
|
+
|
|
331
|
+
for (const [key, mat] of Object.entries(MATERIALS)) {
|
|
332
|
+
const selected = feature.material === key ? 'selected' : '';
|
|
333
|
+
html += `<option value="${key}" ${selected}>${mat.label}</option>`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
html += `
|
|
337
|
+
</select>
|
|
338
|
+
<div class="material-info" id="material-info">
|
|
339
|
+
${getMaterialInfoHtml(feature.material || 'Steel')}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
`;
|
|
343
|
+
|
|
344
|
+
content.innerHTML = html;
|
|
345
|
+
|
|
346
|
+
// Attach event listeners
|
|
347
|
+
attachParamListeners();
|
|
348
|
+
attachMaterialListener();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Show only material selector (for non-parameter features)
|
|
353
|
+
* @param {Object} feature - Feature object
|
|
354
|
+
*/
|
|
355
|
+
export function showMaterial(feature) {
|
|
356
|
+
if (!feature) {
|
|
357
|
+
clearParams();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
paramsState.currentFeature = feature;
|
|
362
|
+
const content = document.getElementById('params-content');
|
|
363
|
+
if (!content) return;
|
|
364
|
+
|
|
365
|
+
const html = `
|
|
366
|
+
<div class="material-section">
|
|
367
|
+
<div class="material-label">Material</div>
|
|
368
|
+
<select class="material-select" id="material-select">
|
|
369
|
+
${Object.entries(MATERIALS)
|
|
370
|
+
.map(([key, mat]) => {
|
|
371
|
+
const selected = feature.material === key ? 'selected' : '';
|
|
372
|
+
return `<option value="${key}" ${selected}>${mat.label}</option>`;
|
|
373
|
+
})
|
|
374
|
+
.join('')}
|
|
375
|
+
</select>
|
|
376
|
+
<div class="material-info" id="material-info">
|
|
377
|
+
${getMaterialInfoHtml(feature.material || 'Steel')}
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
`;
|
|
381
|
+
|
|
382
|
+
content.innerHTML = html;
|
|
383
|
+
attachMaterialListener();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Clear the parameters display
|
|
388
|
+
*/
|
|
389
|
+
export function clearParams() {
|
|
390
|
+
paramsState.currentFeature = null;
|
|
391
|
+
const content = document.getElementById('params-content');
|
|
392
|
+
if (content) {
|
|
393
|
+
content.innerHTML = `
|
|
394
|
+
<div class="params-empty">
|
|
395
|
+
<p>Select a feature to see parameters</p>
|
|
396
|
+
</div>
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Register callback for parameter changes
|
|
403
|
+
* @param {Function} callback - Called with (feature, paramName, newValue)
|
|
404
|
+
*/
|
|
405
|
+
export function onParamChange(callback) {
|
|
406
|
+
paramsState.onParamChangeCallback = callback;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Register callback for material changes
|
|
411
|
+
* @param {Function} callback - Called with (feature, material)
|
|
412
|
+
*/
|
|
413
|
+
export function onMaterialChange(callback) {
|
|
414
|
+
paramsState.onMaterialChangeCallback = callback;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get material info (density, color)
|
|
419
|
+
* @param {string} materialName - Material key
|
|
420
|
+
* @returns {Object} Material object
|
|
421
|
+
*/
|
|
422
|
+
export function getMaterial(materialName) {
|
|
423
|
+
return MATERIALS[materialName] || MATERIALS.Steel;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get all materials
|
|
428
|
+
* @returns {Object} All materials
|
|
429
|
+
*/
|
|
430
|
+
export function getMaterials() {
|
|
431
|
+
return { ...MATERIALS };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Internal: Generate material info HTML
|
|
436
|
+
*/
|
|
437
|
+
function getMaterialInfoHtml(materialName) {
|
|
438
|
+
const mat = MATERIALS[materialName] || MATERIALS.Steel;
|
|
439
|
+
const colorHex = mat.color.toString(16).padStart(6, '0');
|
|
440
|
+
|
|
441
|
+
return `
|
|
442
|
+
<div class="material-info-row">
|
|
443
|
+
<span class="material-info-label">Density:</span>
|
|
444
|
+
<span>${mat.density} g/cm³</span>
|
|
445
|
+
</div>
|
|
446
|
+
<div class="material-info-row">
|
|
447
|
+
<span class="material-info-label">Color:</span>
|
|
448
|
+
<span>
|
|
449
|
+
<div class="material-color-preview" style="background-color: #${colorHex};"></div>
|
|
450
|
+
#${colorHex}
|
|
451
|
+
</span>
|
|
452
|
+
</div>
|
|
453
|
+
`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Internal: Attach parameter input listeners
|
|
458
|
+
*/
|
|
459
|
+
function attachParamListeners() {
|
|
460
|
+
const paramInputs = document.querySelectorAll('.param-input');
|
|
461
|
+
|
|
462
|
+
paramInputs.forEach((input) => {
|
|
463
|
+
input.addEventListener('change', () => {
|
|
464
|
+
if (!paramsState.currentFeature) return;
|
|
465
|
+
|
|
466
|
+
const paramName = input.dataset.param;
|
|
467
|
+
const newValue = parseFloat(input.value);
|
|
468
|
+
|
|
469
|
+
if (!isNaN(newValue)) {
|
|
470
|
+
paramsState.currentFeature.params[paramName] = newValue;
|
|
471
|
+
|
|
472
|
+
if (paramsState.onParamChangeCallback) {
|
|
473
|
+
paramsState.onParamChangeCallback(
|
|
474
|
+
paramsState.currentFeature,
|
|
475
|
+
paramName,
|
|
476
|
+
newValue
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Real-time preview on input (optional)
|
|
483
|
+
input.addEventListener('input', () => {
|
|
484
|
+
// Could trigger a live preview here
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Internal: Attach material select listener
|
|
491
|
+
*/
|
|
492
|
+
function attachMaterialListener() {
|
|
493
|
+
const materialSelect = document.getElementById('material-select');
|
|
494
|
+
if (!materialSelect) return;
|
|
495
|
+
|
|
496
|
+
materialSelect.addEventListener('change', () => {
|
|
497
|
+
if (!paramsState.currentFeature) return;
|
|
498
|
+
|
|
499
|
+
const materialName = materialSelect.value;
|
|
500
|
+
paramsState.currentFeature.material = materialName;
|
|
501
|
+
|
|
502
|
+
// Update material info display
|
|
503
|
+
const materialInfo = document.getElementById('material-info');
|
|
504
|
+
if (materialInfo) {
|
|
505
|
+
materialInfo.innerHTML = getMaterialInfoHtml(materialName);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (paramsState.onMaterialChangeCallback) {
|
|
509
|
+
paramsState.onMaterialChangeCallback(paramsState.currentFeature, materialName);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export default {
|
|
515
|
+
initParams,
|
|
516
|
+
showParams,
|
|
517
|
+
showMaterial,
|
|
518
|
+
clearParams,
|
|
519
|
+
onParamChange,
|
|
520
|
+
onMaterialChange,
|
|
521
|
+
getMaterial,
|
|
522
|
+
getMaterials,
|
|
523
|
+
};
|