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,1321 @@
|
|
|
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
|
+
<title>DUO cycleWASH Project Browser</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
16
|
+
background-color: #0d1117;
|
|
17
|
+
color: #c9d1d9;
|
|
18
|
+
height: 100vh;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Top bar */
|
|
25
|
+
.top-bar {
|
|
26
|
+
background-color: #161b22;
|
|
27
|
+
border-bottom: 1px solid #30363d;
|
|
28
|
+
padding: 12px 16px;
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: 16px;
|
|
33
|
+
flex-wrap: wrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.title {
|
|
37
|
+
font-size: 18px;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
color: #ff8c00;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 8px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.stats {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 24px;
|
|
48
|
+
font-size: 13px;
|
|
49
|
+
color: #8b949e;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.stat-item {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 6px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.stat-value {
|
|
59
|
+
color: #ff8c00;
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.search-box {
|
|
64
|
+
flex: 1;
|
|
65
|
+
min-width: 200px;
|
|
66
|
+
max-width: 300px;
|
|
67
|
+
padding: 6px 12px;
|
|
68
|
+
background-color: #0d1117;
|
|
69
|
+
border: 1px solid #30363d;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
color: #c9d1d9;
|
|
72
|
+
font-size: 13px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.search-box:focus {
|
|
76
|
+
outline: none;
|
|
77
|
+
border-color: #ff8c00;
|
|
78
|
+
box-shadow: 0 0 0 2px rgba(255, 140, 0, 0.1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Main container */
|
|
82
|
+
.main {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex: 1;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Sidebar */
|
|
89
|
+
.sidebar {
|
|
90
|
+
width: 320px;
|
|
91
|
+
background-color: #161b22;
|
|
92
|
+
border-right: 1px solid #30363d;
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.sidebar-header {
|
|
99
|
+
padding: 12px 16px;
|
|
100
|
+
border-bottom: 1px solid #30363d;
|
|
101
|
+
font-size: 12px;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
color: #8b949e;
|
|
104
|
+
text-transform: uppercase;
|
|
105
|
+
letter-spacing: 0.5px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.tree-container {
|
|
109
|
+
flex: 1;
|
|
110
|
+
overflow-y: auto;
|
|
111
|
+
overflow-x: hidden;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.tree-container::-webkit-scrollbar {
|
|
115
|
+
width: 8px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.tree-container::-webkit-scrollbar-track {
|
|
119
|
+
background-color: transparent;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.tree-container::-webkit-scrollbar-thumb {
|
|
123
|
+
background-color: #30363d;
|
|
124
|
+
border-radius: 4px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.tree-container::-webkit-scrollbar-thumb:hover {
|
|
128
|
+
background-color: #484f58;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Tree styles */
|
|
132
|
+
.tree-item {
|
|
133
|
+
list-style: none;
|
|
134
|
+
padding: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.tree-item-label {
|
|
138
|
+
padding: 6px 8px;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
user-select: none;
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 8px;
|
|
144
|
+
font-size: 13px;
|
|
145
|
+
transition: background-color 0.15s;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.tree-item-label:hover {
|
|
149
|
+
background-color: #262c36;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.tree-item-label.selected {
|
|
153
|
+
background-color: #1f6feb;
|
|
154
|
+
color: #fff;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.tree-toggle {
|
|
158
|
+
width: 16px;
|
|
159
|
+
height: 16px;
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
flex-shrink: 0;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
font-size: 10px;
|
|
166
|
+
color: #8b949e;
|
|
167
|
+
user-select: none;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.tree-toggle.collapsed::before {
|
|
171
|
+
content: '▶';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.tree-toggle.expanded::before {
|
|
175
|
+
content: '▼';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.tree-toggle.no-toggle {
|
|
179
|
+
visibility: hidden;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.tree-icon {
|
|
183
|
+
width: 16px;
|
|
184
|
+
height: 16px;
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
flex-shrink: 0;
|
|
189
|
+
font-size: 14px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.tree-text {
|
|
193
|
+
flex: 1;
|
|
194
|
+
min-width: 0;
|
|
195
|
+
overflow: hidden;
|
|
196
|
+
text-overflow: ellipsis;
|
|
197
|
+
white-space: nowrap;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.tree-children {
|
|
201
|
+
list-style: none;
|
|
202
|
+
padding-left: 16px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.tree-children.hidden {
|
|
206
|
+
display: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Content area */
|
|
210
|
+
.content {
|
|
211
|
+
flex: 1;
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
overflow: hidden;
|
|
215
|
+
background-color: #0d1117;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.content-empty {
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
justify-content: center;
|
|
222
|
+
height: 100%;
|
|
223
|
+
color: #6e7681;
|
|
224
|
+
text-align: center;
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
gap: 16px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.content-empty-icon {
|
|
230
|
+
font-size: 48px;
|
|
231
|
+
opacity: 0.5;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.content-panel {
|
|
235
|
+
flex: 1;
|
|
236
|
+
overflow-y: auto;
|
|
237
|
+
padding: 24px;
|
|
238
|
+
display: flex;
|
|
239
|
+
flex-direction: column;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.content-panel::-webkit-scrollbar {
|
|
243
|
+
width: 8px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.content-panel::-webkit-scrollbar-track {
|
|
247
|
+
background-color: transparent;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.content-panel::-webkit-scrollbar-thumb {
|
|
251
|
+
background-color: #30363d;
|
|
252
|
+
border-radius: 4px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.content-panel::-webkit-scrollbar-thumb:hover {
|
|
256
|
+
background-color: #484f58;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.part-header {
|
|
260
|
+
margin-bottom: 24px;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.part-title {
|
|
264
|
+
font-size: 28px;
|
|
265
|
+
font-weight: 600;
|
|
266
|
+
color: #ff8c00;
|
|
267
|
+
margin-bottom: 8px;
|
|
268
|
+
word-break: break-word;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.part-meta {
|
|
272
|
+
display: flex;
|
|
273
|
+
gap: 16px;
|
|
274
|
+
font-size: 13px;
|
|
275
|
+
color: #8b949e;
|
|
276
|
+
flex-wrap: wrap;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.part-section {
|
|
280
|
+
margin-bottom: 20px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.part-section-title {
|
|
284
|
+
font-size: 14px;
|
|
285
|
+
font-weight: 600;
|
|
286
|
+
color: #ff8c00;
|
|
287
|
+
margin-bottom: 12px;
|
|
288
|
+
display: flex;
|
|
289
|
+
align-items: center;
|
|
290
|
+
gap: 8px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.part-section-content {
|
|
294
|
+
padding-left: 16px;
|
|
295
|
+
border-left: 2px solid #30363d;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.part-description {
|
|
299
|
+
color: #c9d1d9;
|
|
300
|
+
line-height: 1.6;
|
|
301
|
+
font-size: 13px;
|
|
302
|
+
margin-bottom: 8px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.part-parent-path {
|
|
306
|
+
display: inline-block;
|
|
307
|
+
background-color: #161b22;
|
|
308
|
+
padding: 6px 10px;
|
|
309
|
+
border-radius: 4px;
|
|
310
|
+
font-family: 'Monaco', 'Courier New', monospace;
|
|
311
|
+
font-size: 12px;
|
|
312
|
+
color: #79c0ff;
|
|
313
|
+
margin-bottom: 12px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.buttons {
|
|
317
|
+
display: flex;
|
|
318
|
+
gap: 12px;
|
|
319
|
+
flex-wrap: wrap;
|
|
320
|
+
margin-top: 20px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.btn {
|
|
324
|
+
padding: 10px 16px;
|
|
325
|
+
border: 1px solid #30363d;
|
|
326
|
+
background-color: #161b22;
|
|
327
|
+
color: #c9d1d9;
|
|
328
|
+
border-radius: 4px;
|
|
329
|
+
cursor: pointer;
|
|
330
|
+
font-size: 13px;
|
|
331
|
+
font-weight: 500;
|
|
332
|
+
transition: all 0.2s;
|
|
333
|
+
text-decoration: none;
|
|
334
|
+
display: inline-block;
|
|
335
|
+
text-align: center;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.btn:hover {
|
|
339
|
+
background-color: #262c36;
|
|
340
|
+
border-color: #484f58;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.btn-primary {
|
|
344
|
+
background-color: #ff8c00;
|
|
345
|
+
color: #000;
|
|
346
|
+
border-color: #ff8c00;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.btn-primary:hover {
|
|
350
|
+
background-color: #ffb143;
|
|
351
|
+
border-color: #ffb143;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.badge {
|
|
355
|
+
display: inline-block;
|
|
356
|
+
background-color: #30363d;
|
|
357
|
+
color: #c9d1d9;
|
|
358
|
+
padding: 4px 8px;
|
|
359
|
+
border-radius: 12px;
|
|
360
|
+
font-size: 11px;
|
|
361
|
+
font-weight: 500;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.badge-assembly {
|
|
365
|
+
background-color: rgba(255, 140, 0, 0.15);
|
|
366
|
+
color: #ff8c00;
|
|
367
|
+
border: 1px solid rgba(255, 140, 0, 0.3);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.badge-part {
|
|
371
|
+
background-color: rgba(88, 166, 255, 0.15);
|
|
372
|
+
color: #58a6ff;
|
|
373
|
+
border: 1px solid rgba(88, 166, 255, 0.3);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.badge-hardware {
|
|
377
|
+
background-color: rgba(79, 195, 247, 0.15);
|
|
378
|
+
color: #4fc3f7;
|
|
379
|
+
border: 1px solid rgba(79, 195, 247, 0.3);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.badge-purchased {
|
|
383
|
+
background-color: rgba(165, 142, 251, 0.15);
|
|
384
|
+
color: #d2a8ff;
|
|
385
|
+
border: 1px solid rgba(165, 142, 251, 0.3);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* Responsive */
|
|
389
|
+
@media (max-width: 768px) {
|
|
390
|
+
.main {
|
|
391
|
+
flex-direction: column;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.sidebar {
|
|
395
|
+
width: 100%;
|
|
396
|
+
border-right: none;
|
|
397
|
+
border-bottom: 1px solid #30363d;
|
|
398
|
+
max-height: 40vh;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.content {
|
|
402
|
+
flex: 1;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.stats {
|
|
406
|
+
gap: 12px;
|
|
407
|
+
font-size: 12px;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.top-bar {
|
|
411
|
+
flex-direction: column;
|
|
412
|
+
align-items: stretch;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.search-box {
|
|
416
|
+
max-width: none;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
</style>
|
|
420
|
+
</head>
|
|
421
|
+
<body>
|
|
422
|
+
<!-- Top bar with stats and search -->
|
|
423
|
+
<div class="top-bar">
|
|
424
|
+
<div class="title">
|
|
425
|
+
📦 DUO cycleWASH Project Browser
|
|
426
|
+
</div>
|
|
427
|
+
<div class="stats">
|
|
428
|
+
<div class="stat-item">
|
|
429
|
+
<span>Total Parts:</span>
|
|
430
|
+
<span class="stat-value">473</span>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="stat-item">
|
|
433
|
+
<span>Assemblies:</span>
|
|
434
|
+
<span class="stat-value">32</span>
|
|
435
|
+
</div>
|
|
436
|
+
<div class="stat-item">
|
|
437
|
+
<span>Sub-Assemblies:</span>
|
|
438
|
+
<span class="stat-value">8</span>
|
|
439
|
+
</div>
|
|
440
|
+
<div class="stat-item">
|
|
441
|
+
<span>Hardware:</span>
|
|
442
|
+
<span class="stat-value">~80</span>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
<input type="text" id="searchBox" class="search-box" placeholder="Search parts...">
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<!-- Main layout -->
|
|
449
|
+
<div class="main">
|
|
450
|
+
<!-- Left sidebar: Tree -->
|
|
451
|
+
<div class="sidebar">
|
|
452
|
+
<div class="sidebar-header">Project Structure</div>
|
|
453
|
+
<div class="tree-container">
|
|
454
|
+
<ul id="tree" class="tree-item"></ul>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<!-- Right content area -->
|
|
459
|
+
<div class="content">
|
|
460
|
+
<div id="contentArea" class="content-empty">
|
|
461
|
+
<div class="content-empty-icon">🔍</div>
|
|
462
|
+
<div>Select a part to view details</div>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
|
|
467
|
+
<script>
|
|
468
|
+
// ============================================================
|
|
469
|
+
// Data structure: DUO cycleWASH Project
|
|
470
|
+
// ============================================================
|
|
471
|
+
|
|
472
|
+
const projectData = {
|
|
473
|
+
name: 'DUO Anlage',
|
|
474
|
+
type: 'assembly',
|
|
475
|
+
children: [
|
|
476
|
+
{
|
|
477
|
+
name: 'Gestell (Frame)',
|
|
478
|
+
type: 'folder',
|
|
479
|
+
parts: [
|
|
480
|
+
{ name: 'Lasche1.ipt', type: 'part', size: '256 KB' },
|
|
481
|
+
{ name: 'Anschraubplatte11.ipt', type: 'part', size: '145 KB' },
|
|
482
|
+
{ name: 'Befestigung Elektronik.ipt', type: 'part', size: '178 KB' },
|
|
483
|
+
{ name: 'TrägerHöhe1.ipt', type: 'part', size: '234 KB' },
|
|
484
|
+
{ name: 'TrägerHöhe1-gespiegelt.ipt', type: 'part', size: '234 KB' },
|
|
485
|
+
{ name: 'Anschraubplatte3.ipt', type: 'part', size: '142 KB' },
|
|
486
|
+
{ name: 'DIN EN 10305-5 - Seitenträger1.ipt', type: 'part', size: '567 KB' },
|
|
487
|
+
{ name: 'DIN EN 10305-5 - Seitenträger2.ipt', type: 'part', size: '589 KB' },
|
|
488
|
+
{ name: 'DIN EN 10305-5 - Seitenträger3.ipt', type: 'part', size: '567 KB' },
|
|
489
|
+
{ name: 'DIN EN 10305-5 - Seitenträger4.ipt', type: 'part', size: '589 KB' },
|
|
490
|
+
{ name: 'TrägerLänge1.ipt', type: 'part', size: '312 KB' },
|
|
491
|
+
{ name: 'TrägerBreite1.ipt', type: 'part', size: '287 KB' },
|
|
492
|
+
{ name: 'TrägerHöheWanne.ipt', type: 'part', size: '198 KB' },
|
|
493
|
+
{ name: 'Abschlussblech Träger1.ipt', type: 'part', size: '156 KB' },
|
|
494
|
+
{ name: 'DIN EN 10305-5 - Träger Lenkersicherung.ipt', type: 'part', size: '423 KB' },
|
|
495
|
+
{ name: 'DIN EN 10305-5 - 20 x 20 x 2 - Träger-E-Kette.ipt', type: 'part', size: '356 KB' },
|
|
496
|
+
{ name: 'DIN EN 10305-5 - Seitenträger2-2.ipt', type: 'part', size: '478 KB' },
|
|
497
|
+
{ name: 'DIN EN 10305-4 - E235 - 8 x 1 - 300.ipt', type: 'part', size: '289 KB' },
|
|
498
|
+
{ name: 'Anschraubplatte3_1.ipt', type: 'part', size: '142 KB' },
|
|
499
|
+
{ name: 'Anschraubplatte2.ipt', type: 'part', size: '134 KB' },
|
|
500
|
+
{ name: 'DIN EN 10305-5 - Seitenträger1-gebohrt.ipt', type: 'part', size: '534 KB' },
|
|
501
|
+
],
|
|
502
|
+
assemblies: [
|
|
503
|
+
{ name: 'Rahmen_Seite.iam', type: 'assembly' },
|
|
504
|
+
{ name: 'Rahmen_Seite2.iam', type: 'assembly' },
|
|
505
|
+
]
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
name: 'Waschbürsten (Wash Brushes)',
|
|
509
|
+
type: 'folder',
|
|
510
|
+
parts: [
|
|
511
|
+
{ name: 'Bürstenblech unten.ipt', type: 'part', size: '234 KB' },
|
|
512
|
+
{ name: 'Bürstenblech oben.ipt', type: 'part', size: '234 KB' },
|
|
513
|
+
{ name: 'bürstenblech1.ipt', type: 'part', size: '198 KB' },
|
|
514
|
+
{ name: 'bürstenblech2.ipt', type: 'part', size: '210 KB' },
|
|
515
|
+
{ name: 'Blech Bürstenmodul1.ipt', type: 'part', size: '167 KB' },
|
|
516
|
+
{ name: 'Blech Bürstenmodul2.ipt', type: 'part', size: '167 KB' },
|
|
517
|
+
{ name: 'Blech Bürstenmodul3.ipt', type: 'part', size: '167 KB' },
|
|
518
|
+
{ name: 'Blech Bürstenmodul4.ipt', type: 'part', size: '167 KB' },
|
|
519
|
+
{ name: 'Blech Bürstenmodul5.ipt', type: 'part', size: '167 KB' },
|
|
520
|
+
{ name: 'Blech Bürstenmodul6.ipt', type: 'part', size: '167 KB' },
|
|
521
|
+
{ name: 'Blech Bürstenmodul7.ipt', type: 'part', size: '167 KB' },
|
|
522
|
+
{ name: 'Riemen1.ipt', type: 'part', size: '145 KB' },
|
|
523
|
+
{ name: 'Riemen2.ipt', type: 'part', size: '145 KB' },
|
|
524
|
+
{ name: 'Riemen3.ipt', type: 'part', size: '145 KB' },
|
|
525
|
+
{ name: 'TrägerWB1.ipt', type: 'part', size: '189 KB' },
|
|
526
|
+
{ name: 'TrägerWB2.ipt', type: 'part', size: '189 KB' },
|
|
527
|
+
{ name: 'TrägerWB3.ipt', type: 'part', size: '189 KB' },
|
|
528
|
+
{ name: 'Anschraubplatte.ipt', type: 'part', size: '134 KB' },
|
|
529
|
+
{ name: 'Abstandsblech.ipt', type: 'part', size: '98 KB' },
|
|
530
|
+
{ name: 'Justierblech1.ipt', type: 'part', size: '112 KB' },
|
|
531
|
+
{ name: 'Initiatorblech.ipt', type: 'part', size: '156 KB' },
|
|
532
|
+
{ name: 'Initiatorblech2.ipt', type: 'part', size: '156 KB' },
|
|
533
|
+
{ name: 'Bürsten-Halter.ipt', type: 'part', size: '267 KB' },
|
|
534
|
+
{ name: 'Blech starrer Riemen.ipt', type: 'part', size: '145 KB' },
|
|
535
|
+
{ name: 'starrer Riemen2.ipt', type: 'part', size: '156 KB' },
|
|
536
|
+
{ name: 'Spritzschutz Motor.ipt', type: 'part', size: '187 KB' },
|
|
537
|
+
{ name: 'Blech Ventilator2.ipt', type: 'part', size: '198 KB' },
|
|
538
|
+
{ name: 'Blech energiekette2.ipt', type: 'part', size: '201 KB' },
|
|
539
|
+
{ name: 'Adapter.ipt', type: 'part', size: '112 KB' },
|
|
540
|
+
],
|
|
541
|
+
assemblies: [
|
|
542
|
+
{ name: 'SBG-Bürste Seite1.iam', type: 'assembly' },
|
|
543
|
+
{ name: 'BG-Bürstenmodul.iam', type: 'assembly' },
|
|
544
|
+
{ name: 'BG-Bürste Seite1 KD-169051.iam', type: 'assembly' },
|
|
545
|
+
]
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: 'Raddreheinheit (Wheel Rotation)',
|
|
549
|
+
type: 'folder',
|
|
550
|
+
parts: [
|
|
551
|
+
{ name: 'BlechRaddreheinheit-1-V5.ipt', type: 'part', size: '423 KB' },
|
|
552
|
+
{ name: 'BlechRaddreheinheit-3-V5.ipt', type: 'part', size: '478 KB' },
|
|
553
|
+
{ name: 'BlechRaddreheinheit-4-V5.ipt', type: 'part', size: '456 KB' },
|
|
554
|
+
{ name: 'BlechMotoraufnahme-V4.ipt', type: 'part', size: '334 KB' },
|
|
555
|
+
{ name: 'BlechWinkel.ipt', type: 'part', size: '201 KB' },
|
|
556
|
+
{ name: 'BlechWinkel4.ipt', type: 'part', size: '201 KB' },
|
|
557
|
+
{ name: 'Blech Gelenk Winkel.ipt', type: 'part', size: '189 KB' },
|
|
558
|
+
{ name: 'Blech Befestigung.ipt', type: 'part', size: '156 KB' },
|
|
559
|
+
{ name: 'DIN EN 10305-5 - Träger Raddreheinheit1.ipt', type: 'part', size: '345 KB' },
|
|
560
|
+
],
|
|
561
|
+
assemblies: [
|
|
562
|
+
{ name: 'SBG Raddreheinheit-V5.iam', type: 'assembly' },
|
|
563
|
+
]
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: 'Lenkerhalterung (Handlebar Holder)',
|
|
567
|
+
type: 'folder',
|
|
568
|
+
parts: [
|
|
569
|
+
{ name: 'DIN EN 10305-4 pipe.ipt', type: 'part', size: '267 KB' },
|
|
570
|
+
{ name: 'Gewindestange.ipt', type: 'part', size: '145 KB' },
|
|
571
|
+
{ name: 'Lenkerhalterung V1.ipt', type: 'part', size: '298 KB' },
|
|
572
|
+
],
|
|
573
|
+
assemblies: [
|
|
574
|
+
{ name: 'BG-Sattelhalterung-Klemme.iam', type: 'assembly' },
|
|
575
|
+
{ name: 'BG-Lenkerhalterung-kpl-V1.iam', type: 'assembly' },
|
|
576
|
+
{ name: 'BG-Lenkerhalterung-Träger.iam', type: 'assembly' },
|
|
577
|
+
]
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: 'Rad (Wheel)',
|
|
581
|
+
type: 'folder',
|
|
582
|
+
parts: [
|
|
583
|
+
{ name: 'Rollen-Aufnahme_125.ipt', type: 'part', size: '289 KB' },
|
|
584
|
+
{ name: 'Rolle_Rippe.ipt', type: 'part', size: '156 KB' },
|
|
585
|
+
{ name: 'Rollen-Platte.ipt', type: 'part', size: '178 KB' },
|
|
586
|
+
],
|
|
587
|
+
assemblies: []
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: 'Seitenwand (Side Wall)',
|
|
591
|
+
type: 'folder',
|
|
592
|
+
parts: [
|
|
593
|
+
{ name: 'Halteblech-Metall.ipt', type: 'part', size: '234 KB' },
|
|
594
|
+
{ name: 'Parameter Skizze_V3.ipt', type: 'part', size: '89 KB' },
|
|
595
|
+
],
|
|
596
|
+
assemblies: []
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: 'Schiebebürsten (Slide Brushes)',
|
|
600
|
+
type: 'folder',
|
|
601
|
+
parts: [
|
|
602
|
+
{ name: 'Adapter Schiebebürsten.ipt', type: 'part', size: '167 KB' },
|
|
603
|
+
{ name: 'Verstärkung_01.ipt', type: 'part', size: '134 KB' },
|
|
604
|
+
{ name: 'Verstärkung_02.ipt', type: 'part', size: '134 KB' },
|
|
605
|
+
],
|
|
606
|
+
assemblies: []
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
name: 'Ultraschallreiniger (Ultrasonic Cleaner)',
|
|
610
|
+
type: 'folder',
|
|
611
|
+
parts: [
|
|
612
|
+
{ name: 'Gehäuse 3.ipt', type: 'part', size: '567 KB' },
|
|
613
|
+
{ name: 'Gehäuse 5.ipt', type: 'part', size: '612 KB' },
|
|
614
|
+
{ name: 'Deckel3.ipt', type: 'part', size: '423 KB' },
|
|
615
|
+
{ name: 'Deckel5.ipt', type: 'part', size: '467 KB' },
|
|
616
|
+
{ name: 'GewBolzenM4x8.ipt', type: 'part', size: '45 KB' },
|
|
617
|
+
{ name: 'Rohr_01.ipt', type: 'part', size: '178 KB' },
|
|
618
|
+
{ name: 'HYDRA connector.ipt', type: 'part', size: '312 KB' },
|
|
619
|
+
],
|
|
620
|
+
assemblies: [
|
|
621
|
+
{ name: 'Ultraschall analog 3 DG28.iam', type: 'assembly' },
|
|
622
|
+
{ name: 'Ultraschall analog 5 DG28.iam', type: 'assembly' },
|
|
623
|
+
{ name: 'Deckel mit Bolzen.iam', type: 'assembly' },
|
|
624
|
+
]
|
|
625
|
+
},
|
|
626
|
+
]
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// Top-level parts
|
|
630
|
+
const topLevelParts = [
|
|
631
|
+
{ name: 'Leistenbürstenblech.ipt', type: 'part', size: '267 KB' },
|
|
632
|
+
{ name: 'Skizze_E-Ketten_Halterung.ipt', type: 'part', size: '112 KB' },
|
|
633
|
+
{ name: 'Traeger_50x30_Kompressor.ipt', type: 'part', size: '345 KB' },
|
|
634
|
+
{ name: 'Verkleidung Boden Seite_V4.ipt', type: 'part', size: '534 KB' },
|
|
635
|
+
{ name: 'Verkleidung Boden Seite_V4-2.ipt', type: 'part', size: '534 KB' },
|
|
636
|
+
];
|
|
637
|
+
|
|
638
|
+
// Purchased parts (Zukaufteile)
|
|
639
|
+
const purchasedParts = [
|
|
640
|
+
{
|
|
641
|
+
name: 'igus',
|
|
642
|
+
type: 'vendor',
|
|
643
|
+
description: 'Linear guides, telescopic rail systems'
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: 'Interroll',
|
|
647
|
+
type: 'vendor',
|
|
648
|
+
description: 'Roller conveyor systems'
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
name: 'Rittal',
|
|
652
|
+
type: 'vendor',
|
|
653
|
+
description: 'Electrical cabinet solutions'
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: 'WEG Motor',
|
|
657
|
+
type: 'vendor',
|
|
658
|
+
description: 'Gear motors'
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: 'Kipp-Norelem',
|
|
662
|
+
type: 'vendor',
|
|
663
|
+
description: 'Clamps, locks, and positioning elements'
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: 'Zuwa',
|
|
667
|
+
type: 'vendor',
|
|
668
|
+
description: 'Pump systems'
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
name: 'Elektronik Housing',
|
|
672
|
+
type: 'vendor',
|
|
673
|
+
description: 'Electronics enclosure'
|
|
674
|
+
},
|
|
675
|
+
];
|
|
676
|
+
|
|
677
|
+
// Standard hardware (Libraries/Content Center)
|
|
678
|
+
const standardHardware = [
|
|
679
|
+
{ name: 'ISO 4762 Socket Head Cap Screw M3', type: 'hardware' },
|
|
680
|
+
{ name: 'ISO 4762 Socket Head Cap Screw M4', type: 'hardware' },
|
|
681
|
+
{ name: 'ISO 4762 Socket Head Cap Screw M5', type: 'hardware' },
|
|
682
|
+
{ name: 'ISO 4762 Socket Head Cap Screw M6', type: 'hardware' },
|
|
683
|
+
{ name: 'ISO 4762 Socket Head Cap Screw M8', type: 'hardware' },
|
|
684
|
+
{ name: 'ISO 4762 Socket Head Cap Screw M10', type: 'hardware' },
|
|
685
|
+
{ name: 'ISO 4032 Hex Nut M4', type: 'hardware' },
|
|
686
|
+
{ name: 'ISO 4032 Hex Nut M5', type: 'hardware' },
|
|
687
|
+
{ name: 'ISO 4032 Hex Nut M6', type: 'hardware' },
|
|
688
|
+
{ name: 'ISO 4032 Hex Nut M8', type: 'hardware' },
|
|
689
|
+
{ name: 'ISO 4032 Hex Nut M10', type: 'hardware' },
|
|
690
|
+
{ name: 'ISO 7089 Washer 3mm', type: 'hardware' },
|
|
691
|
+
{ name: 'ISO 7089 Washer 4mm', type: 'hardware' },
|
|
692
|
+
{ name: 'ISO 7089 Washer 5mm', type: 'hardware' },
|
|
693
|
+
{ name: 'ISO 7089 Washer 6mm', type: 'hardware' },
|
|
694
|
+
{ name: 'ISO 7089 Washer 8mm', type: 'hardware' },
|
|
695
|
+
{ name: 'ISO 7089 Washer 10mm', type: 'hardware' },
|
|
696
|
+
{ name: 'ISO 10642 Countersunk M4', type: 'hardware' },
|
|
697
|
+
{ name: 'ISO 10642 Countersunk M5', type: 'hardware' },
|
|
698
|
+
{ name: 'ISO 10642 Countersunk M6', type: 'hardware' },
|
|
699
|
+
{ name: 'ISO 7380-1 Button Head Cap M6', type: 'hardware' },
|
|
700
|
+
{ name: 'DIN 6912 Low-Head Cap Screw M4', type: 'hardware' },
|
|
701
|
+
{ name: 'DIN 6912 Low-Head Cap Screw M5', type: 'hardware' },
|
|
702
|
+
{ name: 'DIN 6912 Low-Head Cap Screw M6', type: 'hardware' },
|
|
703
|
+
{ name: 'DIN 6912 Low-Head Cap Screw M8', type: 'hardware' },
|
|
704
|
+
{ name: 'DIN 580 Eye Bolt M8', type: 'hardware' },
|
|
705
|
+
{ name: 'DIN 1587 Cap Nut M4', type: 'hardware' },
|
|
706
|
+
{ name: 'DIN 1587 Cap Nut M5', type: 'hardware' },
|
|
707
|
+
{ name: 'DIN 7349 Heavy Washer 6.4mm', type: 'hardware' },
|
|
708
|
+
{ name: 'Schnorr VS 6 Lock Washer', type: 'hardware' },
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
// ============================================================
|
|
712
|
+
// Utility functions
|
|
713
|
+
// ============================================================
|
|
714
|
+
|
|
715
|
+
function getIcon(type) {
|
|
716
|
+
const icons = {
|
|
717
|
+
'folder': '📁',
|
|
718
|
+
'assembly': '🔩',
|
|
719
|
+
'part': '⚙️',
|
|
720
|
+
'vendor': '🏭',
|
|
721
|
+
'hardware': '🔧',
|
|
722
|
+
};
|
|
723
|
+
return icons[type] || '📄';
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function getBadgeClass(type) {
|
|
727
|
+
const classes = {
|
|
728
|
+
'assembly': 'badge-assembly',
|
|
729
|
+
'part': 'badge-part',
|
|
730
|
+
'hardware': 'badge-hardware',
|
|
731
|
+
'vendor': 'badge-purchased',
|
|
732
|
+
};
|
|
733
|
+
return classes[type] || '';
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function flattenParts(structure, path = 'DUO Anlage') {
|
|
737
|
+
let parts = [];
|
|
738
|
+
|
|
739
|
+
if (structure.children) {
|
|
740
|
+
structure.children.forEach((folder, idx) => {
|
|
741
|
+
const folderPath = path + ' > ' + folder.name;
|
|
742
|
+
|
|
743
|
+
if (folder.parts) {
|
|
744
|
+
folder.parts.forEach(part => {
|
|
745
|
+
parts.push({
|
|
746
|
+
...part,
|
|
747
|
+
path: folderPath,
|
|
748
|
+
parentFolder: folder.name
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (folder.assemblies) {
|
|
754
|
+
folder.assemblies.forEach(asm => {
|
|
755
|
+
parts.push({
|
|
756
|
+
...asm,
|
|
757
|
+
path: folderPath,
|
|
758
|
+
parentFolder: folder.name
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
topLevelParts.forEach(part => {
|
|
766
|
+
parts.push({
|
|
767
|
+
...part,
|
|
768
|
+
path: 'DUO Anlage',
|
|
769
|
+
parentFolder: 'DUO Anlage'
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
purchasedParts.forEach(part => {
|
|
774
|
+
parts.push({
|
|
775
|
+
...part,
|
|
776
|
+
path: 'Zukaufteile (Purchased Parts)',
|
|
777
|
+
parentFolder: 'Zukaufteile'
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
standardHardware.forEach(part => {
|
|
782
|
+
parts.push({
|
|
783
|
+
...part,
|
|
784
|
+
path: 'Standard Hardware (Libraries)',
|
|
785
|
+
parentFolder: 'Standard Hardware'
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
return parts;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// ============================================================
|
|
793
|
+
// Rendering
|
|
794
|
+
// ============================================================
|
|
795
|
+
|
|
796
|
+
let allParts = flattenParts(projectData);
|
|
797
|
+
let selectedPart = null;
|
|
798
|
+
let expandedFolders = new Set();
|
|
799
|
+
|
|
800
|
+
function renderTree(container, data, parent = null) {
|
|
801
|
+
const ul = document.createElement('ul');
|
|
802
|
+
ul.className = 'tree-item';
|
|
803
|
+
|
|
804
|
+
// Render folders and their contents
|
|
805
|
+
if (data.children) {
|
|
806
|
+
data.children.forEach((item, idx) => {
|
|
807
|
+
const li = document.createElement('li');
|
|
808
|
+
li.className = 'tree-item';
|
|
809
|
+
|
|
810
|
+
const label = document.createElement('div');
|
|
811
|
+
label.className = 'tree-item-label';
|
|
812
|
+
label.dataset.itemId = `folder-${idx}`;
|
|
813
|
+
|
|
814
|
+
const toggle = document.createElement('div');
|
|
815
|
+
toggle.className = 'tree-toggle collapsed';
|
|
816
|
+
toggle.onclick = (e) => {
|
|
817
|
+
e.stopPropagation();
|
|
818
|
+
const childrenContainer = li.querySelector('.tree-children');
|
|
819
|
+
const isExpanded = expandedFolders.has(`folder-${idx}`);
|
|
820
|
+
if (isExpanded) {
|
|
821
|
+
expandedFolders.delete(`folder-${idx}`);
|
|
822
|
+
childrenContainer.classList.add('hidden');
|
|
823
|
+
toggle.classList.remove('expanded');
|
|
824
|
+
toggle.classList.add('collapsed');
|
|
825
|
+
} else {
|
|
826
|
+
expandedFolders.add(`folder-${idx}`);
|
|
827
|
+
childrenContainer.classList.remove('hidden');
|
|
828
|
+
toggle.classList.add('expanded');
|
|
829
|
+
toggle.classList.remove('collapsed');
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
const icon = document.createElement('div');
|
|
834
|
+
icon.className = 'tree-icon';
|
|
835
|
+
icon.textContent = getIcon(item.type);
|
|
836
|
+
|
|
837
|
+
const text = document.createElement('div');
|
|
838
|
+
text.className = 'tree-text';
|
|
839
|
+
text.textContent = item.name;
|
|
840
|
+
|
|
841
|
+
label.appendChild(toggle);
|
|
842
|
+
label.appendChild(icon);
|
|
843
|
+
label.appendChild(text);
|
|
844
|
+
|
|
845
|
+
li.appendChild(label);
|
|
846
|
+
|
|
847
|
+
// Children container
|
|
848
|
+
const childrenUl = document.createElement('ul');
|
|
849
|
+
childrenUl.className = 'tree-children hidden';
|
|
850
|
+
|
|
851
|
+
// Add parts
|
|
852
|
+
if (item.parts) {
|
|
853
|
+
item.parts.forEach((part, partIdx) => {
|
|
854
|
+
const partLi = document.createElement('li');
|
|
855
|
+
partLi.className = 'tree-item';
|
|
856
|
+
|
|
857
|
+
const partLabel = document.createElement('div');
|
|
858
|
+
partLabel.className = 'tree-item-label';
|
|
859
|
+
partLabel.dataset.partId = `${item.name}|${part.name}`;
|
|
860
|
+
partLabel.onclick = () => selectPart(part, item.name);
|
|
861
|
+
|
|
862
|
+
const noToggle = document.createElement('div');
|
|
863
|
+
noToggle.className = 'tree-toggle no-toggle';
|
|
864
|
+
|
|
865
|
+
const partIcon = document.createElement('div');
|
|
866
|
+
partIcon.className = 'tree-icon';
|
|
867
|
+
partIcon.textContent = getIcon(part.type);
|
|
868
|
+
|
|
869
|
+
const partText = document.createElement('div');
|
|
870
|
+
partText.className = 'tree-text';
|
|
871
|
+
partText.textContent = part.name;
|
|
872
|
+
|
|
873
|
+
partLabel.appendChild(noToggle);
|
|
874
|
+
partLabel.appendChild(partIcon);
|
|
875
|
+
partLabel.appendChild(partText);
|
|
876
|
+
|
|
877
|
+
partLi.appendChild(partLabel);
|
|
878
|
+
childrenUl.appendChild(partLi);
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Add assemblies
|
|
883
|
+
if (item.assemblies) {
|
|
884
|
+
item.assemblies.forEach((asm, asmIdx) => {
|
|
885
|
+
const asmLi = document.createElement('li');
|
|
886
|
+
asmLi.className = 'tree-item';
|
|
887
|
+
|
|
888
|
+
const asmLabel = document.createElement('div');
|
|
889
|
+
asmLabel.className = 'tree-item-label';
|
|
890
|
+
asmLabel.dataset.partId = `${item.name}|${asm.name}`;
|
|
891
|
+
asmLabel.onclick = () => selectPart(asm, item.name);
|
|
892
|
+
|
|
893
|
+
const noToggle = document.createElement('div');
|
|
894
|
+
noToggle.className = 'tree-toggle no-toggle';
|
|
895
|
+
|
|
896
|
+
const asmIcon = document.createElement('div');
|
|
897
|
+
asmIcon.className = 'tree-icon';
|
|
898
|
+
asmIcon.textContent = getIcon(asm.type);
|
|
899
|
+
|
|
900
|
+
const asmText = document.createElement('div');
|
|
901
|
+
asmText.className = 'tree-text';
|
|
902
|
+
asmText.textContent = asm.name;
|
|
903
|
+
|
|
904
|
+
asmLabel.appendChild(noToggle);
|
|
905
|
+
asmLabel.appendChild(asmIcon);
|
|
906
|
+
asmLabel.appendChild(asmText);
|
|
907
|
+
|
|
908
|
+
asmLi.appendChild(asmLabel);
|
|
909
|
+
childrenUl.appendChild(asmLi);
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
li.appendChild(childrenUl);
|
|
914
|
+
ul.appendChild(li);
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Top-level parts
|
|
919
|
+
topLevelParts.forEach((part, idx) => {
|
|
920
|
+
const li = document.createElement('li');
|
|
921
|
+
li.className = 'tree-item';
|
|
922
|
+
|
|
923
|
+
const label = document.createElement('div');
|
|
924
|
+
label.className = 'tree-item-label';
|
|
925
|
+
label.dataset.partId = `topLevel|${part.name}`;
|
|
926
|
+
label.onclick = () => selectPart(part, 'DUO Anlage');
|
|
927
|
+
|
|
928
|
+
const noToggle = document.createElement('div');
|
|
929
|
+
noToggle.className = 'tree-toggle no-toggle';
|
|
930
|
+
|
|
931
|
+
const icon = document.createElement('div');
|
|
932
|
+
icon.className = 'tree-icon';
|
|
933
|
+
icon.textContent = getIcon(part.type);
|
|
934
|
+
|
|
935
|
+
const text = document.createElement('div');
|
|
936
|
+
text.className = 'tree-text';
|
|
937
|
+
text.textContent = part.name;
|
|
938
|
+
|
|
939
|
+
label.appendChild(noToggle);
|
|
940
|
+
label.appendChild(icon);
|
|
941
|
+
label.appendChild(text);
|
|
942
|
+
|
|
943
|
+
li.appendChild(label);
|
|
944
|
+
ul.appendChild(li);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
// Purchased parts folder
|
|
948
|
+
const purchasedLi = document.createElement('li');
|
|
949
|
+
purchasedLi.className = 'tree-item';
|
|
950
|
+
|
|
951
|
+
const purchasedLabel = document.createElement('div');
|
|
952
|
+
purchasedLabel.className = 'tree-item-label';
|
|
953
|
+
purchasedLabel.dataset.itemId = 'purchased';
|
|
954
|
+
|
|
955
|
+
const purchasedToggle = document.createElement('div');
|
|
956
|
+
purchasedToggle.className = 'tree-toggle collapsed';
|
|
957
|
+
purchasedToggle.onclick = (e) => {
|
|
958
|
+
e.stopPropagation();
|
|
959
|
+
const childrenContainer = purchasedLi.querySelector('.tree-children');
|
|
960
|
+
const isExpanded = expandedFolders.has('purchased');
|
|
961
|
+
if (isExpanded) {
|
|
962
|
+
expandedFolders.delete('purchased');
|
|
963
|
+
childrenContainer.classList.add('hidden');
|
|
964
|
+
purchasedToggle.classList.remove('expanded');
|
|
965
|
+
purchasedToggle.classList.add('collapsed');
|
|
966
|
+
} else {
|
|
967
|
+
expandedFolders.add('purchased');
|
|
968
|
+
childrenContainer.classList.remove('hidden');
|
|
969
|
+
purchasedToggle.classList.add('expanded');
|
|
970
|
+
purchasedToggle.classList.remove('collapsed');
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
const purchasedIcon = document.createElement('div');
|
|
975
|
+
purchasedIcon.className = 'tree-icon';
|
|
976
|
+
purchasedIcon.textContent = getIcon('vendor');
|
|
977
|
+
|
|
978
|
+
const purchasedText = document.createElement('div');
|
|
979
|
+
purchasedText.className = 'tree-text';
|
|
980
|
+
purchasedText.textContent = 'Zukaufteile (Purchased Parts)';
|
|
981
|
+
|
|
982
|
+
purchasedLabel.appendChild(purchasedToggle);
|
|
983
|
+
purchasedLabel.appendChild(purchasedIcon);
|
|
984
|
+
purchasedLabel.appendChild(purchasedText);
|
|
985
|
+
|
|
986
|
+
purchasedLi.appendChild(purchasedLabel);
|
|
987
|
+
|
|
988
|
+
const purchasedUl = document.createElement('ul');
|
|
989
|
+
purchasedUl.className = 'tree-children hidden';
|
|
990
|
+
|
|
991
|
+
purchasedParts.forEach((part) => {
|
|
992
|
+
const partLi = document.createElement('li');
|
|
993
|
+
partLi.className = 'tree-item';
|
|
994
|
+
|
|
995
|
+
const partLabel = document.createElement('div');
|
|
996
|
+
partLabel.className = 'tree-item-label';
|
|
997
|
+
partLabel.dataset.partId = `purchased|${part.name}`;
|
|
998
|
+
partLabel.onclick = () => selectPart(part, 'Zukaufteile');
|
|
999
|
+
|
|
1000
|
+
const noToggle = document.createElement('div');
|
|
1001
|
+
noToggle.className = 'tree-toggle no-toggle';
|
|
1002
|
+
|
|
1003
|
+
const partIcon = document.createElement('div');
|
|
1004
|
+
partIcon.className = 'tree-icon';
|
|
1005
|
+
partIcon.textContent = getIcon(part.type);
|
|
1006
|
+
|
|
1007
|
+
const partText = document.createElement('div');
|
|
1008
|
+
partText.className = 'tree-text';
|
|
1009
|
+
partText.textContent = part.name;
|
|
1010
|
+
|
|
1011
|
+
partLabel.appendChild(noToggle);
|
|
1012
|
+
partLabel.appendChild(partIcon);
|
|
1013
|
+
partLabel.appendChild(partText);
|
|
1014
|
+
|
|
1015
|
+
partLi.appendChild(partLabel);
|
|
1016
|
+
purchasedUl.appendChild(partLi);
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
purchasedLi.appendChild(purchasedUl);
|
|
1020
|
+
ul.appendChild(purchasedLi);
|
|
1021
|
+
|
|
1022
|
+
// Hardware folder
|
|
1023
|
+
const hardwareLi = document.createElement('li');
|
|
1024
|
+
hardwareLi.className = 'tree-item';
|
|
1025
|
+
|
|
1026
|
+
const hardwareLabel = document.createElement('div');
|
|
1027
|
+
hardwareLabel.className = 'tree-item-label';
|
|
1028
|
+
hardwareLabel.dataset.itemId = 'hardware';
|
|
1029
|
+
|
|
1030
|
+
const hardwareToggle = document.createElement('div');
|
|
1031
|
+
hardwareToggle.className = 'tree-toggle collapsed';
|
|
1032
|
+
hardwareToggle.onclick = (e) => {
|
|
1033
|
+
e.stopPropagation();
|
|
1034
|
+
const childrenContainer = hardwareLi.querySelector('.tree-children');
|
|
1035
|
+
const isExpanded = expandedFolders.has('hardware');
|
|
1036
|
+
if (isExpanded) {
|
|
1037
|
+
expandedFolders.delete('hardware');
|
|
1038
|
+
childrenContainer.classList.add('hidden');
|
|
1039
|
+
hardwareToggle.classList.remove('expanded');
|
|
1040
|
+
hardwareToggle.classList.add('collapsed');
|
|
1041
|
+
} else {
|
|
1042
|
+
expandedFolders.add('hardware');
|
|
1043
|
+
childrenContainer.classList.remove('hidden');
|
|
1044
|
+
hardwareToggle.classList.add('expanded');
|
|
1045
|
+
hardwareToggle.classList.remove('collapsed');
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
const hardwareIcon = document.createElement('div');
|
|
1050
|
+
hardwareIcon.className = 'tree-icon';
|
|
1051
|
+
hardwareIcon.textContent = getIcon('hardware');
|
|
1052
|
+
|
|
1053
|
+
const hardwareText = document.createElement('div');
|
|
1054
|
+
hardwareText.className = 'tree-text';
|
|
1055
|
+
hardwareText.textContent = 'Standard Hardware (Libraries)';
|
|
1056
|
+
|
|
1057
|
+
hardwareLabel.appendChild(hardwareToggle);
|
|
1058
|
+
hardwareLabel.appendChild(hardwareIcon);
|
|
1059
|
+
hardwareLabel.appendChild(hardwareText);
|
|
1060
|
+
|
|
1061
|
+
hardwareLi.appendChild(hardwareLabel);
|
|
1062
|
+
|
|
1063
|
+
const hardwareUl = document.createElement('ul');
|
|
1064
|
+
hardwareUl.className = 'tree-children hidden';
|
|
1065
|
+
|
|
1066
|
+
standardHardware.forEach((part) => {
|
|
1067
|
+
const partLi = document.createElement('li');
|
|
1068
|
+
partLi.className = 'tree-item';
|
|
1069
|
+
|
|
1070
|
+
const partLabel = document.createElement('div');
|
|
1071
|
+
partLabel.className = 'tree-item-label';
|
|
1072
|
+
partLabel.dataset.partId = `hardware|${part.name}`;
|
|
1073
|
+
partLabel.onclick = () => selectPart(part, 'Standard Hardware');
|
|
1074
|
+
|
|
1075
|
+
const noToggle = document.createElement('div');
|
|
1076
|
+
noToggle.className = 'tree-toggle no-toggle';
|
|
1077
|
+
|
|
1078
|
+
const partIcon = document.createElement('div');
|
|
1079
|
+
partIcon.className = 'tree-icon';
|
|
1080
|
+
partIcon.textContent = getIcon(part.type);
|
|
1081
|
+
|
|
1082
|
+
const partText = document.createElement('div');
|
|
1083
|
+
partText.className = 'tree-text';
|
|
1084
|
+
partText.textContent = part.name;
|
|
1085
|
+
|
|
1086
|
+
partLabel.appendChild(noToggle);
|
|
1087
|
+
partLabel.appendChild(partIcon);
|
|
1088
|
+
partLabel.appendChild(partText);
|
|
1089
|
+
|
|
1090
|
+
partLi.appendChild(partLabel);
|
|
1091
|
+
hardwareUl.appendChild(partLi);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
hardwareLi.appendChild(hardwareUl);
|
|
1095
|
+
ul.appendChild(hardwareLi);
|
|
1096
|
+
|
|
1097
|
+
container.appendChild(ul);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function selectPart(part, parentFolder) {
|
|
1101
|
+
selectedPart = { ...part, parentFolder };
|
|
1102
|
+
|
|
1103
|
+
// Update selected styling
|
|
1104
|
+
document.querySelectorAll('.tree-item-label.selected').forEach(el => {
|
|
1105
|
+
el.classList.remove('selected');
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
const partId = parentFolder === 'DUO Anlage' ? `topLevel|${part.name}` :
|
|
1109
|
+
parentFolder === 'Zukaufteile' ? `purchased|${part.name}` :
|
|
1110
|
+
parentFolder === 'Standard Hardware' ? `hardware|${part.name}` :
|
|
1111
|
+
`${parentFolder}|${part.name}`;
|
|
1112
|
+
|
|
1113
|
+
const label = document.querySelector(`[data-part-id="${partId}"]`);
|
|
1114
|
+
if (label) label.classList.add('selected');
|
|
1115
|
+
|
|
1116
|
+
// Update content
|
|
1117
|
+
renderPartDetail(part, parentFolder);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function renderPartDetail(part, parentFolder) {
|
|
1121
|
+
const contentArea = document.getElementById('contentArea');
|
|
1122
|
+
contentArea.innerHTML = '';
|
|
1123
|
+
|
|
1124
|
+
const panel = document.createElement('div');
|
|
1125
|
+
panel.className = 'content-panel';
|
|
1126
|
+
|
|
1127
|
+
// Header
|
|
1128
|
+
const header = document.createElement('div');
|
|
1129
|
+
header.className = 'part-header';
|
|
1130
|
+
|
|
1131
|
+
const title = document.createElement('div');
|
|
1132
|
+
title.className = 'part-title';
|
|
1133
|
+
title.textContent = part.name;
|
|
1134
|
+
|
|
1135
|
+
const meta = document.createElement('div');
|
|
1136
|
+
meta.className = 'part-meta';
|
|
1137
|
+
|
|
1138
|
+
const typeBadge = document.createElement('span');
|
|
1139
|
+
typeBadge.className = `badge ${getBadgeClass(part.type)}`;
|
|
1140
|
+
typeBadge.textContent = part.type.charAt(0).toUpperCase() + part.type.slice(1);
|
|
1141
|
+
|
|
1142
|
+
meta.appendChild(typeBadge);
|
|
1143
|
+
|
|
1144
|
+
if (part.size) {
|
|
1145
|
+
const sizeSpan = document.createElement('span');
|
|
1146
|
+
sizeSpan.textContent = `Size: ${part.size}`;
|
|
1147
|
+
meta.appendChild(sizeSpan);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (part.description) {
|
|
1151
|
+
const descSpan = document.createElement('span');
|
|
1152
|
+
descSpan.textContent = `Type: ${part.description}`;
|
|
1153
|
+
meta.appendChild(descSpan);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
header.appendChild(title);
|
|
1157
|
+
header.appendChild(meta);
|
|
1158
|
+
panel.appendChild(header);
|
|
1159
|
+
|
|
1160
|
+
// Assembly membership
|
|
1161
|
+
if (parentFolder && parentFolder !== 'DUO Anlage' && parentFolder !== 'Zukaufteile' && parentFolder !== 'Standard Hardware') {
|
|
1162
|
+
const section = document.createElement('div');
|
|
1163
|
+
section.className = 'part-section';
|
|
1164
|
+
|
|
1165
|
+
const sectionTitle = document.createElement('div');
|
|
1166
|
+
sectionTitle.className = 'part-section-title';
|
|
1167
|
+
sectionTitle.innerHTML = '📦 Assembly Location';
|
|
1168
|
+
|
|
1169
|
+
const content = document.createElement('div');
|
|
1170
|
+
content.className = 'part-section-content';
|
|
1171
|
+
|
|
1172
|
+
const path = document.createElement('div');
|
|
1173
|
+
path.className = 'part-parent-path';
|
|
1174
|
+
path.textContent = `DUO Anlage > ${parentFolder}`;
|
|
1175
|
+
|
|
1176
|
+
content.appendChild(path);
|
|
1177
|
+
section.appendChild(sectionTitle);
|
|
1178
|
+
section.appendChild(content);
|
|
1179
|
+
panel.appendChild(section);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Details section
|
|
1183
|
+
const detailsSection = document.createElement('div');
|
|
1184
|
+
detailsSection.className = 'part-section';
|
|
1185
|
+
|
|
1186
|
+
const detailsTitle = document.createElement('div');
|
|
1187
|
+
detailsTitle.className = 'part-section-title';
|
|
1188
|
+
detailsTitle.innerHTML = '📋 Details';
|
|
1189
|
+
|
|
1190
|
+
const detailsContent = document.createElement('div');
|
|
1191
|
+
detailsContent.className = 'part-section-content';
|
|
1192
|
+
|
|
1193
|
+
const details = [];
|
|
1194
|
+
details.push(`<div class="part-description"><strong>Name:</strong> ${part.name}</div>`);
|
|
1195
|
+
details.push(`<div class="part-description"><strong>Type:</strong> ${part.type.charAt(0).toUpperCase() + part.type.slice(1)}</div>`);
|
|
1196
|
+
|
|
1197
|
+
if (part.size) {
|
|
1198
|
+
details.push(`<div class="part-description"><strong>File Size:</strong> ${part.size}</div>`);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (part.description) {
|
|
1202
|
+
details.push(`<div class="part-description"><strong>Supplier/Description:</strong> ${part.description}</div>`);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (parentFolder) {
|
|
1206
|
+
details.push(`<div class="part-description"><strong>Parent Assembly:</strong> ${parentFolder}</div>`);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
detailsContent.innerHTML = details.join('');
|
|
1210
|
+
detailsSection.appendChild(detailsTitle);
|
|
1211
|
+
detailsSection.appendChild(detailsContent);
|
|
1212
|
+
panel.appendChild(detailsSection);
|
|
1213
|
+
|
|
1214
|
+
// Actions
|
|
1215
|
+
const actions = document.createElement('div');
|
|
1216
|
+
actions.className = 'part-section';
|
|
1217
|
+
|
|
1218
|
+
const actionsTitle = document.createElement('div');
|
|
1219
|
+
actionsTitle.className = 'part-section-title';
|
|
1220
|
+
actionsTitle.innerHTML = '⚡ Actions';
|
|
1221
|
+
|
|
1222
|
+
const buttonsContainer = document.createElement('div');
|
|
1223
|
+
buttonsContainer.className = 'buttons';
|
|
1224
|
+
|
|
1225
|
+
const guideBtn = document.createElement('a');
|
|
1226
|
+
guideBtn.className = 'btn btn-primary';
|
|
1227
|
+
guideBtn.href = `duo-rebuild-guide.html#${part.name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}`;
|
|
1228
|
+
guideBtn.textContent = '📖 View Rebuild Guide';
|
|
1229
|
+
|
|
1230
|
+
const cadBtn = document.createElement('button');
|
|
1231
|
+
cadBtn.className = 'btn';
|
|
1232
|
+
cadBtn.textContent = '🔧 Open in cycleCAD';
|
|
1233
|
+
cadBtn.onclick = () => {
|
|
1234
|
+
alert(`Opening "${part.name}" in cycleCAD...\n\nThis would integrate with Autodesk Inventor to open the part in the main assembly.`);
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
buttonsContainer.appendChild(guideBtn);
|
|
1238
|
+
buttonsContainer.appendChild(cadBtn);
|
|
1239
|
+
|
|
1240
|
+
actions.appendChild(actionsTitle);
|
|
1241
|
+
actions.appendChild(buttonsContainer);
|
|
1242
|
+
panel.appendChild(actions);
|
|
1243
|
+
|
|
1244
|
+
contentArea.appendChild(panel);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// ============================================================
|
|
1248
|
+
// Search functionality
|
|
1249
|
+
// ============================================================
|
|
1250
|
+
|
|
1251
|
+
function filterTree(searchTerm) {
|
|
1252
|
+
searchTerm = searchTerm.toLowerCase();
|
|
1253
|
+
|
|
1254
|
+
if (!searchTerm) {
|
|
1255
|
+
// Show all items
|
|
1256
|
+
document.querySelectorAll('.tree-item-label').forEach(label => {
|
|
1257
|
+
label.style.display = '';
|
|
1258
|
+
});
|
|
1259
|
+
document.querySelectorAll('.tree-children').forEach(container => {
|
|
1260
|
+
container.style.display = '';
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Filter parts
|
|
1266
|
+
let matchedItems = new Set();
|
|
1267
|
+
|
|
1268
|
+
allParts.forEach(part => {
|
|
1269
|
+
if (part.name.toLowerCase().includes(searchTerm)) {
|
|
1270
|
+
matchedItems.add(part.name);
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
// Update visibility
|
|
1275
|
+
document.querySelectorAll('.tree-item-label').forEach(label => {
|
|
1276
|
+
const partId = label.dataset.partId;
|
|
1277
|
+
if (partId) {
|
|
1278
|
+
const [folder, name] = partId.split('|');
|
|
1279
|
+
if (matchedItems.has(name)) {
|
|
1280
|
+
label.style.display = '';
|
|
1281
|
+
// Make sure parent is visible
|
|
1282
|
+
let parent = label.closest('.tree-children');
|
|
1283
|
+
while (parent) {
|
|
1284
|
+
parent.style.display = '';
|
|
1285
|
+
parent = parent.parentElement?.closest('.tree-children');
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
label.style.display = 'none';
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// ============================================================
|
|
1295
|
+
// Initialization
|
|
1296
|
+
// ============================================================
|
|
1297
|
+
|
|
1298
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1299
|
+
const treeContainer = document.getElementById('tree');
|
|
1300
|
+
renderTree(treeContainer, projectData);
|
|
1301
|
+
|
|
1302
|
+
const searchBox = document.getElementById('searchBox');
|
|
1303
|
+
searchBox.addEventListener('input', (e) => {
|
|
1304
|
+
filterTree(e.target.value);
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
// Show initial state
|
|
1308
|
+
const contentArea = document.getElementById('contentArea');
|
|
1309
|
+
contentArea.innerHTML = `
|
|
1310
|
+
<div style="padding: 24px; text-align: center; color: #8b949e;">
|
|
1311
|
+
<div style="font-size: 48px; margin-bottom: 16px;">🔍</div>
|
|
1312
|
+
<div style="font-size: 16px;">Select a part to view details</div>
|
|
1313
|
+
<div style="margin-top: 12px; font-size: 13px; color: #6e7681;">
|
|
1314
|
+
Browse the project tree on the left to explore the DUO cycleWASH structure
|
|
1315
|
+
</div>
|
|
1316
|
+
</div>
|
|
1317
|
+
`;
|
|
1318
|
+
});
|
|
1319
|
+
</script>
|
|
1320
|
+
</body>
|
|
1321
|
+
</html>
|