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,1138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventor File Parser โ ES Module (v2)
|
|
3
|
+
* Parses .ipt (part) and .iam (assembly) files directly in the browser
|
|
4
|
+
* Zero dependencies, pure OLE2/CFB container parsing + multi-stream analysis
|
|
5
|
+
* Generates Fusion 360 + cycleCAD reconstruction guides
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// CONSTANTS & HELPERS
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
const OLE2_SIGNATURE = 0xD0CF11E0;
|
|
13
|
+
const SECTOR_SIZE = 512;
|
|
14
|
+
const MINI_SECTOR_SIZE = 64;
|
|
15
|
+
const DIR_ENTRY_SIZE = 128;
|
|
16
|
+
const FAT_SECTOR_ID = 0xFFFFFFFD;
|
|
17
|
+
const ENDOFCHAIN = 0xFFFFFFFE;
|
|
18
|
+
const FREECHAIN = 0xFFFFFFFF;
|
|
19
|
+
|
|
20
|
+
// Feature type patterns (ASCII strings to search in binary)
|
|
21
|
+
const FEATURE_PATTERNS = [
|
|
22
|
+
{ pattern: 'ExtrudeFeature', type: 'Extrude', icon: '๐ฆ', color: '#3fb950', f360: 'Extrude', cycleCAD: 'extrudeProfile' },
|
|
23
|
+
{ pattern: 'RevolveFeature', type: 'Revolve', icon: '๐', color: '#d29922', f360: 'Revolve', cycleCAD: 'revolveProfile' },
|
|
24
|
+
{ pattern: 'HoleFeature', type: 'Hole', icon: 'โญ', color: '#f85149', f360: 'Hole', cycleCAD: 'createHole' },
|
|
25
|
+
{ pattern: 'FilletFeature', type: 'Fillet', icon: 'โ', color: '#a371f7', f360: 'Fillet', cycleCAD: 'applyFillet' },
|
|
26
|
+
{ pattern: 'ChamferFeature', type: 'Chamfer', icon: 'โ', color: '#db61a2', f360: 'Chamfer', cycleCAD: 'applyChamfer' },
|
|
27
|
+
{ pattern: 'Sketch2D', type: 'Sketch', icon: 'โ๏ธ', color: '#58a6ff', f360: 'Create Sketch', cycleCAD: 'startSketch' },
|
|
28
|
+
{ pattern: 'Sketch3D', type: '3D Sketch', icon: 'โ๏ธ', color: '#58a6ff', f360: 'Create 3D Sketch', cycleCAD: 'startSketch3D' },
|
|
29
|
+
{ pattern: 'RectangularPatternFeature', type: 'RectPattern', icon: 'โ', color: '#58a6ff', f360: 'Rectangular Pattern', cycleCAD: 'rectPattern' },
|
|
30
|
+
{ pattern: 'CircularPatternFeature', type: 'CircPattern', icon: 'โ', color: '#58a6ff', f360: 'Circular Pattern', cycleCAD: 'circPattern' },
|
|
31
|
+
{ pattern: 'MirrorFeature', type: 'Mirror', icon: 'โ', color: '#79c0ff', f360: 'Mirror', cycleCAD: 'mirror' },
|
|
32
|
+
{ pattern: 'SweepFeature', type: 'Sweep', icon: '๐', color: '#a371f7', f360: 'Sweep', cycleCAD: 'sweepProfile' },
|
|
33
|
+
{ pattern: 'LoftFeature', type: 'Loft', icon: '๐', color: '#3fb950', f360: 'Loft', cycleCAD: 'loftProfile' },
|
|
34
|
+
{ pattern: 'ShellFeature', type: 'Shell', icon: '๐', color: '#d29922', f360: 'Shell', cycleCAD: 'shellBody' },
|
|
35
|
+
{ pattern: 'ThreadFeature', type: 'Thread', icon: '๐ฉ', color: '#8b949e', f360: 'Thread', cycleCAD: 'addThread' },
|
|
36
|
+
{ pattern: 'WorkPlane', type: 'WorkPlane', icon: '๐', color: '#8b949e', f360: 'Construction Plane', cycleCAD: 'createWorkPlane' },
|
|
37
|
+
{ pattern: 'WorkAxis', type: 'WorkAxis', icon: '๐', color: '#8b949e', f360: 'Construction Axis', cycleCAD: 'createWorkAxis' },
|
|
38
|
+
{ pattern: 'WorkPoint', type: 'WorkPoint', icon: 'โข', color: '#8b949e', f360: 'Construction Point', cycleCAD: 'createWorkPoint' },
|
|
39
|
+
{ pattern: 'BooleanFeature', type: 'Boolean', icon: 'โ๏ธ', color: '#f85149', f360: 'Combine', cycleCAD: 'booleanOp' },
|
|
40
|
+
{ pattern: 'CutFeature', type: 'Cut', icon: 'โ๏ธ', color: '#f85149', f360: 'Extrude (Cut)', cycleCAD: 'cutExtrude' },
|
|
41
|
+
{ pattern: 'SplitFeature', type: 'Split', icon: 'โ๏ธ', color: '#f85149', f360: 'Split Body', cycleCAD: 'splitBody' },
|
|
42
|
+
// Sheet metal features
|
|
43
|
+
{ pattern: 'FlangeFeature', type: 'Flange', icon: '๐', color: '#d29922', f360: 'Flange', cycleCAD: 'addFlange' },
|
|
44
|
+
{ pattern: 'BendFeature', type: 'Bend', icon: 'โช', color: '#d29922', f360: 'Bend', cycleCAD: 'addBend' },
|
|
45
|
+
{ pattern: 'HemFeature', type: 'Hem', icon: 'โคต', color: '#d29922', f360: 'Hem', cycleCAD: 'addHem' },
|
|
46
|
+
{ pattern: 'FoldFeature', type: 'Fold', icon: '๐', color: '#d29922', f360: 'Fold', cycleCAD: 'addFold' },
|
|
47
|
+
{ pattern: 'FlatPattern', type: 'FlatPattern', icon: '๐', color: '#3fb950', f360: 'Create Flat Pattern', cycleCAD: 'createFlatPattern' },
|
|
48
|
+
{ pattern: 'ContourFlangeFeature', type: 'ContourFlange', icon: '๐', color: '#d29922', f360: 'Contour Flange', cycleCAD: 'addContourFlange' },
|
|
49
|
+
{ pattern: 'FaceFeature', type: 'Face', icon: 'โข', color: '#3fb950', f360: 'Face', cycleCAD: 'createFace' },
|
|
50
|
+
{ pattern: 'UnfoldFeature', type: 'Unfold', icon: 'โ', color: '#d29922', f360: 'Unfold', cycleCAD: 'unfold' },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// Constraint type patterns for assemblies
|
|
54
|
+
const CONSTRAINT_PATTERNS = [
|
|
55
|
+
{ pattern: 'MateConstraint', f360: 'Joint > Rigid', type: 'Mate' },
|
|
56
|
+
{ pattern: 'FlushConstraint', f360: 'Joint > Planar', type: 'Flush' },
|
|
57
|
+
{ pattern: 'AngleConstraint', f360: 'Joint > Revolute', type: 'Angle' },
|
|
58
|
+
{ pattern: 'InsertConstraint', f360: 'Joint > Cylindrical', type: 'Insert' },
|
|
59
|
+
{ pattern: 'TangentConstraint', f360: 'Joint > Slider', type: 'Tangent' },
|
|
60
|
+
{ pattern: 'CoincidentConstraint', f360: 'Joint > Rigid', type: 'Coincident' },
|
|
61
|
+
{ pattern: 'ParallelConstraint', f360: 'Joint > Rigid', type: 'Parallel' },
|
|
62
|
+
{ pattern: 'PerpendicularConstraint', f360: 'Joint > Rigid', type: 'Perpendicular' },
|
|
63
|
+
{ pattern: 'ConcentricConstraint', f360: 'Joint > Revolute', type: 'Concentric' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Inventor template โ part type mapping
|
|
67
|
+
const TEMPLATE_MAP = {
|
|
68
|
+
'SheetMetal': { type: 'Sheet Metal', f360Tab: 'SHEET METAL', icon: '๐' },
|
|
69
|
+
'Standard': { type: 'Solid Part', f360Tab: 'SOLID', icon: '๐ฆ' },
|
|
70
|
+
'Weldment': { type: 'Weldment', f360Tab: 'SOLID', icon: '๐ง' },
|
|
71
|
+
'Mold': { type: 'Mold Design', f360Tab: 'SOLID', icon: '๐ญ' },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// OLE2/CFB PARSER
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
function parseOLE2(buffer) {
|
|
79
|
+
const view = new DataView(buffer.buffer || buffer);
|
|
80
|
+
const bufArr = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
81
|
+
|
|
82
|
+
// Validate signature
|
|
83
|
+
const sig1 = view.getUint32(0, false);
|
|
84
|
+
if (sig1 !== 0xD0CF11E0) {
|
|
85
|
+
throw new Error('Invalid OLE2 signature โ not an Inventor file');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sectorSizePow = view.getUint16(30, true);
|
|
89
|
+
const sectorSize = 1 << sectorSizePow;
|
|
90
|
+
const totalFATSectors = view.getUint32(44, true);
|
|
91
|
+
const dirStartSector = view.getInt32(48, true);
|
|
92
|
+
const version = `${view.getUint16(26, true)}.${view.getUint16(24, true)}`;
|
|
93
|
+
|
|
94
|
+
// Build FAT from header DIFAT entries
|
|
95
|
+
const fat = [];
|
|
96
|
+
for (let i = 0; i < Math.min(totalFATSectors, 109); i++) {
|
|
97
|
+
const fatSectorID = view.getInt32(76 + i * 4, true);
|
|
98
|
+
if (fatSectorID < 0) break;
|
|
99
|
+
const offset = (fatSectorID + 1) * sectorSize;
|
|
100
|
+
if (offset + sectorSize > bufArr.length) break;
|
|
101
|
+
for (let j = 0; j < sectorSize / 4; j++) {
|
|
102
|
+
fat.push(view.getInt32(offset + j * 4, true));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Read sector chain
|
|
107
|
+
function readChain(startSector, maxSize = Infinity) {
|
|
108
|
+
const chunks = [];
|
|
109
|
+
let sector = startSector;
|
|
110
|
+
let totalRead = 0;
|
|
111
|
+
let safety = 0;
|
|
112
|
+
while (sector >= 0 && sector < fat.length && safety < 50000 && totalRead < maxSize) {
|
|
113
|
+
const offset = (sector + 1) * sectorSize;
|
|
114
|
+
if (offset + sectorSize > bufArr.length) break;
|
|
115
|
+
chunks.push(bufArr.slice(offset, offset + sectorSize));
|
|
116
|
+
totalRead += sectorSize;
|
|
117
|
+
sector = fat[sector];
|
|
118
|
+
safety++;
|
|
119
|
+
}
|
|
120
|
+
const result = new Uint8Array(totalRead);
|
|
121
|
+
let pos = 0;
|
|
122
|
+
for (const c of chunks) { result.set(c, pos); pos += c.length; }
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Read directory entries
|
|
127
|
+
const dirData = readChain(dirStartSector, 200 * sectorSize);
|
|
128
|
+
const entries = [];
|
|
129
|
+
for (let i = 0; i < dirData.length; i += 128) {
|
|
130
|
+
const nameLen = dirData[i + 64] | (dirData[i + 65] << 8);
|
|
131
|
+
let name = '';
|
|
132
|
+
for (let c = 0; c < Math.min(nameLen, 64) - 2; c += 2) {
|
|
133
|
+
const ch = dirData[i + c] | (dirData[i + c + 1] << 8);
|
|
134
|
+
if (ch === 0) break;
|
|
135
|
+
name += String.fromCharCode(ch);
|
|
136
|
+
}
|
|
137
|
+
const type = dirData[i + 66];
|
|
138
|
+
const startSector = dirData[i + 116] | (dirData[i + 117] << 8) | (dirData[i + 118] << 16) | (dirData[i + 119] << 24);
|
|
139
|
+
const size = dirData[i + 120] | (dirData[i + 121] << 8) | (dirData[i + 122] << 16) | (dirData[i + 123] << 24);
|
|
140
|
+
if (type > 0 && name) entries.push({ name, type, startSector, size });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
version,
|
|
145
|
+
sectorSize,
|
|
146
|
+
entries,
|
|
147
|
+
fat,
|
|
148
|
+
getStream(name) {
|
|
149
|
+
const entry = entries.find(e => e.name === name && e.type === 2);
|
|
150
|
+
if (!entry || entry.size <= 0) return null;
|
|
151
|
+
const raw = readChain(entry.startSector, entry.size + sectorSize);
|
|
152
|
+
return raw.slice(0, Math.min(raw.length, entry.size));
|
|
153
|
+
},
|
|
154
|
+
getAllStreams() {
|
|
155
|
+
return entries.filter(e => e.type === 2 && e.size > 0);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// UFRxDoc PARSER โ This is where the REAL feature tree lives!
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
function parseUFRxDoc(ole2) {
|
|
165
|
+
const data = ole2.getStream('UFRxDoc');
|
|
166
|
+
if (!data) return null;
|
|
167
|
+
|
|
168
|
+
const result = {
|
|
169
|
+
template: '',
|
|
170
|
+
partType: 'Standard',
|
|
171
|
+
originalPath: '',
|
|
172
|
+
savedFrom: '',
|
|
173
|
+
savedOn: '',
|
|
174
|
+
fileSchema: '',
|
|
175
|
+
softwareSchema: '',
|
|
176
|
+
features: [],
|
|
177
|
+
parameters: [],
|
|
178
|
+
references: [],
|
|
179
|
+
bodies: [],
|
|
180
|
+
exportStates: [],
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Decode as UTF-16LE (Inventor uses Windows UTF-16)
|
|
184
|
+
const utf16 = new TextDecoder('utf-16le', { fatal: false }).decode(data);
|
|
185
|
+
|
|
186
|
+
// Also get ASCII view for binary searches
|
|
187
|
+
const ascii = new TextDecoder('ascii', { fatal: false }).decode(data);
|
|
188
|
+
|
|
189
|
+
// Extract template type
|
|
190
|
+
const templateMatch = utf16.match(/Templates\\[^\\]*\\([^.]+)\.ipt/i) ||
|
|
191
|
+
utf16.match(/Templates\\[^\\]*\\([^.]+)\.iam/i);
|
|
192
|
+
if (templateMatch) {
|
|
193
|
+
result.template = templateMatch[1];
|
|
194
|
+
// Detect part type from template
|
|
195
|
+
for (const [key, val] of Object.entries(TEMPLATE_MAP)) {
|
|
196
|
+
if (result.template.toLowerCase().includes(key.toLowerCase())) {
|
|
197
|
+
result.partType = val.type;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Extract version info
|
|
204
|
+
const schemaMatch = utf16.match(/FileSchema:\s*([\d.]+)/);
|
|
205
|
+
if (schemaMatch) result.fileSchema = schemaMatch[1];
|
|
206
|
+
|
|
207
|
+
const softwareMatch = utf16.match(/SoftwareSchema:\s*([\d.]+)/);
|
|
208
|
+
if (softwareMatch) result.softwareSchema = softwareMatch[1];
|
|
209
|
+
|
|
210
|
+
const savedFromMatch = utf16.match(/SavedFrom:\s*([^\0]+)/);
|
|
211
|
+
if (savedFromMatch) result.savedFrom = savedFromMatch[1].trim();
|
|
212
|
+
|
|
213
|
+
const savedOnMatch = utf16.match(/SavedOn:\s*([^\0]+)/);
|
|
214
|
+
if (savedOnMatch) result.savedOn = savedOnMatch[1].trim();
|
|
215
|
+
|
|
216
|
+
// Extract original file path
|
|
217
|
+
const pathMatch = utf16.match(/([A-Z]:\\[^\0]{10,}\.ipt)/i) ||
|
|
218
|
+
utf16.match(/([A-Z]:\\[^\0]{10,}\.iam)/i);
|
|
219
|
+
if (pathMatch) result.originalPath = pathMatch[1];
|
|
220
|
+
|
|
221
|
+
// Extract Body references
|
|
222
|
+
const bodyRegex = /Body\s*\(\s*Body\s*\)/g;
|
|
223
|
+
let bodyMatch;
|
|
224
|
+
while ((bodyMatch = utf16.matchAll(/Body\s*\(([^)]*)\)/g))) {
|
|
225
|
+
for (const m of bodyMatch) {
|
|
226
|
+
result.bodies.push(m[1] || 'Body');
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Extract Export/Feature states from UFRxDoc
|
|
232
|
+
const exportRegex = /Export([A-Za-z]+)/g;
|
|
233
|
+
let exportMatch2;
|
|
234
|
+
while ((exportMatch2 = exportRegex.exec(utf16)) !== null) {
|
|
235
|
+
const name = exportMatch2[1];
|
|
236
|
+
if (name !== 'GlobalState' && !result.exportStates.includes(name)) {
|
|
237
|
+
result.exportStates.push(name);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Extract parameter references from UFRxDoc
|
|
242
|
+
const paramRegex = /([A-Za-z_]\w*)\s*\(\s*Parameter\s*\)\s*(\d+)/g;
|
|
243
|
+
let paramMatch2;
|
|
244
|
+
while ((paramMatch2 = paramRegex.exec(utf16)) !== null) {
|
|
245
|
+
result.parameters.push({
|
|
246
|
+
name: paramMatch2[1],
|
|
247
|
+
id: parseInt(paramMatch2[2]),
|
|
248
|
+
source: 'UFRxDoc'
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Extract file references (for assemblies or linked parts)
|
|
253
|
+
const refRegex = /([A-Za-z0-9_\- ]+\.ipt)/gi;
|
|
254
|
+
let refMatch;
|
|
255
|
+
while ((refMatch = refRegex.exec(utf16)) !== null) {
|
|
256
|
+
const ref = refMatch[1].trim();
|
|
257
|
+
if (ref.length > 4 && !result.references.includes(ref)) {
|
|
258
|
+
result.references.push(ref);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Scan for feature keywords in both UFRxDoc and ascii view
|
|
263
|
+
for (const fp of FEATURE_PATTERNS) {
|
|
264
|
+
// Check UTF-16
|
|
265
|
+
if (utf16.includes(fp.pattern)) {
|
|
266
|
+
const existing = result.features.find(f => f.type === fp.type);
|
|
267
|
+
if (!existing) {
|
|
268
|
+
result.features.push({
|
|
269
|
+
type: fp.type,
|
|
270
|
+
pattern: fp.pattern,
|
|
271
|
+
icon: fp.icon,
|
|
272
|
+
color: fp.color,
|
|
273
|
+
f360: fp.f360,
|
|
274
|
+
cycleCAD: fp.cycleCAD,
|
|
275
|
+
source: 'UFRxDoc',
|
|
276
|
+
count: (utf16.match(new RegExp(fp.pattern, 'g')) || []).length
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Check ASCII
|
|
281
|
+
if (ascii.includes(fp.pattern)) {
|
|
282
|
+
const existing = result.features.find(f => f.type === fp.type);
|
|
283
|
+
if (!existing) {
|
|
284
|
+
result.features.push({
|
|
285
|
+
type: fp.type,
|
|
286
|
+
pattern: fp.pattern,
|
|
287
|
+
icon: fp.icon,
|
|
288
|
+
color: fp.color,
|
|
289
|
+
f360: fp.f360,
|
|
290
|
+
cycleCAD: fp.cycleCAD,
|
|
291
|
+
source: 'UFRxDoc-ascii',
|
|
292
|
+
count: (ascii.match(new RegExp(fp.pattern, 'g')) || []).length
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// MULTI-STREAM FEATURE SCANNER
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
function scanAllStreams(ole2) {
|
|
306
|
+
const allFeatures = [];
|
|
307
|
+
const allParams = [];
|
|
308
|
+
const fileRefs = [];
|
|
309
|
+
const constraints = [];
|
|
310
|
+
|
|
311
|
+
const streams = ole2.getAllStreams();
|
|
312
|
+
|
|
313
|
+
for (const entry of streams) {
|
|
314
|
+
if (entry.size < 50) continue;
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const data = ole2.getStream(entry.name);
|
|
318
|
+
if (!data) continue;
|
|
319
|
+
|
|
320
|
+
// ASCII decode
|
|
321
|
+
const ascii = new TextDecoder('ascii', { fatal: false }).decode(data);
|
|
322
|
+
// UTF-16 decode for wider coverage
|
|
323
|
+
const utf16 = new TextDecoder('utf-16le', { fatal: false }).decode(data);
|
|
324
|
+
|
|
325
|
+
// Scan for features
|
|
326
|
+
for (const fp of FEATURE_PATTERNS) {
|
|
327
|
+
const asciiMatches = (ascii.match(new RegExp(fp.pattern, 'g')) || []).length;
|
|
328
|
+
const utf16Matches = (utf16.match(new RegExp(fp.pattern, 'g')) || []).length;
|
|
329
|
+
const count = Math.max(asciiMatches, utf16Matches);
|
|
330
|
+
if (count > 0) {
|
|
331
|
+
allFeatures.push({
|
|
332
|
+
type: fp.type,
|
|
333
|
+
pattern: fp.pattern,
|
|
334
|
+
icon: fp.icon,
|
|
335
|
+
color: fp.color,
|
|
336
|
+
f360: fp.f360,
|
|
337
|
+
cycleCAD: fp.cycleCAD,
|
|
338
|
+
source: entry.name,
|
|
339
|
+
count
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Scan for .ipt/.iam references
|
|
345
|
+
const iptRefs = [...utf16.matchAll(/([A-Za-z0-9_\- ]+\.ipt)/gi)];
|
|
346
|
+
const iamRefs = [...utf16.matchAll(/([A-Za-z0-9_\- ]+\.iam)/gi)];
|
|
347
|
+
for (const ref of [...iptRefs, ...iamRefs]) {
|
|
348
|
+
const name = ref[1].trim();
|
|
349
|
+
if (name.length > 4 && !fileRefs.includes(name)) fileRefs.push(name);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Scan for constraints
|
|
353
|
+
for (const cp of CONSTRAINT_PATTERNS) {
|
|
354
|
+
if (ascii.includes(cp.pattern) || utf16.includes(cp.pattern)) {
|
|
355
|
+
if (!constraints.find(c => c.type === cp.type)) {
|
|
356
|
+
constraints.push({ type: cp.type, f360: cp.f360, source: entry.name });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
} catch (e) {
|
|
362
|
+
// Skip unreadable streams
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Deduplicate features by type, keep highest count
|
|
367
|
+
const featureMap = new Map();
|
|
368
|
+
for (const f of allFeatures) {
|
|
369
|
+
const existing = featureMap.get(f.type);
|
|
370
|
+
if (!existing || f.count > existing.count) {
|
|
371
|
+
featureMap.set(f.type, f);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
features: Array.from(featureMap.values()),
|
|
377
|
+
fileRefs,
|
|
378
|
+
constraints
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ============================================================================
|
|
383
|
+
// FUSION 360 RECONSTRUCTION GUIDE GENERATOR
|
|
384
|
+
// ============================================================================
|
|
385
|
+
|
|
386
|
+
function generateFusion360Guide(parsedData) {
|
|
387
|
+
const steps = [];
|
|
388
|
+
let stepNum = 1;
|
|
389
|
+
|
|
390
|
+
// Step 1: Create new document
|
|
391
|
+
const partType = parsedData.ufrxDoc?.partType || 'Standard';
|
|
392
|
+
const isSheetMetal = partType === 'Sheet Metal';
|
|
393
|
+
|
|
394
|
+
steps.push({
|
|
395
|
+
step: stepNum++,
|
|
396
|
+
action: 'Create New Design',
|
|
397
|
+
f360: isSheetMetal
|
|
398
|
+
? 'File โ New Design โ Switch to SHEET METAL tab in toolbar'
|
|
399
|
+
: 'File โ New Design (Parametric design mode)',
|
|
400
|
+
cycleCAD: isSheetMetal ? 'newSheetMetalPart()' : 'newPart()',
|
|
401
|
+
tip: `Original: Inventor ${parsedData.ufrxDoc?.savedFrom || 'unknown version'}`,
|
|
402
|
+
icon: '๐'
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Step 2: Set up parameters
|
|
406
|
+
if (parsedData.parameters.length > 0) {
|
|
407
|
+
steps.push({
|
|
408
|
+
step: stepNum++,
|
|
409
|
+
action: 'Define User Parameters',
|
|
410
|
+
f360: `Modify โ Change Parameters โ Add each:\n${parsedData.parameters.map(p => ` โข ${p.name} = ${p.id || '?'} mm`).join('\n')}`,
|
|
411
|
+
cycleCAD: parsedData.parameters.map(p => `setParam('${p.name}', ${p.id || 0})`).join('; '),
|
|
412
|
+
tip: `${parsedData.parameters.length} parameters found. Set these FIRST โ features reference them.`,
|
|
413
|
+
icon: 'โ๏ธ'
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Step 3: Set material thickness for sheet metal
|
|
418
|
+
if (isSheetMetal) {
|
|
419
|
+
const thicknessParam = parsedData.parameters.find(p =>
|
|
420
|
+
p.name.toLowerCase().includes('stรคrke') || p.name.toLowerCase().includes('thickness') || p.name.toLowerCase() === 't'
|
|
421
|
+
);
|
|
422
|
+
steps.push({
|
|
423
|
+
step: stepNum++,
|
|
424
|
+
action: 'Set Sheet Metal Rules',
|
|
425
|
+
f360: `SHEET METAL tab โ Sheet Metal Rules โ Thickness: ${thicknessParam ? thicknessParam.id + ' mm' : '(check parameters)'}`,
|
|
426
|
+
cycleCAD: `setSheetMetalRules({ thickness: ${thicknessParam?.id || 1} })`,
|
|
427
|
+
tip: 'Set thickness before creating any sheet metal features',
|
|
428
|
+
icon: '๐'
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Step 4+: Features in order
|
|
433
|
+
const features = parsedData.allFeatures || [];
|
|
434
|
+
const featureOrder = [
|
|
435
|
+
'Sketch', '3D Sketch', 'WorkPlane', 'WorkAxis', 'WorkPoint',
|
|
436
|
+
'Face', 'Extrude', 'Revolve', 'Sweep', 'Loft',
|
|
437
|
+
'Flange', 'ContourFlange', 'Bend', 'Hem', 'Fold',
|
|
438
|
+
'Hole', 'Cut', 'Boolean', 'Split',
|
|
439
|
+
'Fillet', 'Chamfer', 'Shell', 'Thread',
|
|
440
|
+
'Mirror', 'RectPattern', 'CircPattern',
|
|
441
|
+
'Unfold', 'FlatPattern'
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
// Sort features by logical build order
|
|
445
|
+
const sortedFeatures = [...features].sort((a, b) => {
|
|
446
|
+
const aIdx = featureOrder.indexOf(a.type);
|
|
447
|
+
const bIdx = featureOrder.indexOf(b.type);
|
|
448
|
+
return (aIdx === -1 ? 999 : aIdx) - (bIdx === -1 ? 999 : bIdx);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
for (const feat of sortedFeatures) {
|
|
452
|
+
const plural = feat.count > 1 ? ` (ร${feat.count})` : '';
|
|
453
|
+
steps.push({
|
|
454
|
+
step: stepNum++,
|
|
455
|
+
action: `${feat.icon} ${feat.type}${plural}`,
|
|
456
|
+
f360: `${isSheetMetal ? 'SHEET METAL' : 'SOLID'} tab โ ${feat.f360}`,
|
|
457
|
+
cycleCAD: `${feat.cycleCAD}()`,
|
|
458
|
+
tip: feat.count > 1 ? `Repeat ${feat.count} times or use pattern` : '',
|
|
459
|
+
icon: feat.icon,
|
|
460
|
+
color: feat.color
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Final step: flat pattern for sheet metal
|
|
465
|
+
if (isSheetMetal && !features.find(f => f.type === 'FlatPattern')) {
|
|
466
|
+
steps.push({
|
|
467
|
+
step: stepNum++,
|
|
468
|
+
action: '๐ Create Flat Pattern',
|
|
469
|
+
f360: 'SHEET METAL tab โ Create Flat Pattern โ Verify all bends unfold correctly',
|
|
470
|
+
cycleCAD: 'createFlatPattern()',
|
|
471
|
+
tip: 'Verify flat pattern for DXF export to laser/punch',
|
|
472
|
+
icon: '๐'
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Verify step
|
|
477
|
+
steps.push({
|
|
478
|
+
step: stepNum++,
|
|
479
|
+
action: 'โ
Verify & Compare',
|
|
480
|
+
f360: 'Inspect โ Measure to check key dimensions match original Inventor model',
|
|
481
|
+
cycleCAD: 'verifyDimensions()',
|
|
482
|
+
tip: 'Compare with original parameters to ensure accuracy',
|
|
483
|
+
icon: 'โ
'
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return steps;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// ASSEMBLY RECONSTRUCTION GUIDE
|
|
491
|
+
// ============================================================================
|
|
492
|
+
|
|
493
|
+
function generateAssemblyGuide(parsedData) {
|
|
494
|
+
const steps = [];
|
|
495
|
+
let stepNum = 1;
|
|
496
|
+
|
|
497
|
+
steps.push({
|
|
498
|
+
step: stepNum++,
|
|
499
|
+
action: 'Create New Assembly',
|
|
500
|
+
f360: 'File โ New Design โ Start with empty component',
|
|
501
|
+
cycleCAD: 'newAssembly()',
|
|
502
|
+
tip: 'Fusion 360 uses a top-down assembly workflow',
|
|
503
|
+
icon: '๐๏ธ'
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Add component steps
|
|
507
|
+
const components = parsedData.fileRefs || [];
|
|
508
|
+
const iptFiles = components.filter(c => c.toLowerCase().endsWith('.ipt'));
|
|
509
|
+
const iamFiles = components.filter(c => c.toLowerCase().endsWith('.iam'));
|
|
510
|
+
|
|
511
|
+
if (iamFiles.length > 0) {
|
|
512
|
+
steps.push({
|
|
513
|
+
step: stepNum++,
|
|
514
|
+
action: `Import ${iamFiles.length} Sub-Assemblies`,
|
|
515
|
+
f360: iamFiles.map(f => ` โข Assemble โ Insert โ ${f}`).join('\n'),
|
|
516
|
+
cycleCAD: iamFiles.map(f => `insertComponent('${f}')`).join('; '),
|
|
517
|
+
tip: 'Import sub-assemblies first, then individual parts',
|
|
518
|
+
icon: '๐ฆ'
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (iptFiles.length > 0) {
|
|
523
|
+
steps.push({
|
|
524
|
+
step: stepNum++,
|
|
525
|
+
action: `Import ${iptFiles.length} Parts`,
|
|
526
|
+
f360: `Assemble โ Insert for each part:\n${iptFiles.slice(0, 10).map(f => ` โข ${f}`).join('\n')}${iptFiles.length > 10 ? `\n โข ... and ${iptFiles.length - 10} more` : ''}`,
|
|
527
|
+
cycleCAD: iptFiles.map(f => `insertComponent('${f}')`).join('; '),
|
|
528
|
+
tip: `Total: ${iptFiles.length} part files`,
|
|
529
|
+
icon: '๐ง'
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Constraint steps
|
|
534
|
+
const constraints = parsedData.constraints || [];
|
|
535
|
+
if (constraints.length > 0) {
|
|
536
|
+
steps.push({
|
|
537
|
+
step: stepNum++,
|
|
538
|
+
action: `Apply ${constraints.length} Joint Types`,
|
|
539
|
+
f360: constraints.map(c => ` โข ${c.type} โ Fusion 360: ${c.f360}`).join('\n'),
|
|
540
|
+
cycleCAD: 'applyJoints()',
|
|
541
|
+
tip: 'Inventor Constraints โ Fusion 360 Joints. Some may need manual adjustment.',
|
|
542
|
+
icon: '๐'
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
steps.push({
|
|
547
|
+
step: stepNum++,
|
|
548
|
+
action: 'โ
Verify Assembly',
|
|
549
|
+
f360: 'Inspect โ Interference to check for clashes',
|
|
550
|
+
cycleCAD: 'verifyAssembly()',
|
|
551
|
+
tip: 'Check for interference and verify motion',
|
|
552
|
+
icon: 'โ
'
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return steps;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ============================================================================
|
|
559
|
+
// MAIN PARSER
|
|
560
|
+
// ============================================================================
|
|
561
|
+
|
|
562
|
+
export async function parseInventorFile(file) {
|
|
563
|
+
const buffer = await file.arrayBuffer();
|
|
564
|
+
const arr = new Uint8Array(buffer);
|
|
565
|
+
|
|
566
|
+
const filename = file.name;
|
|
567
|
+
const isIPT = filename.toLowerCase().endsWith('.ipt');
|
|
568
|
+
const isIAM = filename.toLowerCase().endsWith('.iam');
|
|
569
|
+
const fileType = isIPT ? 'ipt' : isIAM ? 'iam' : 'unknown';
|
|
570
|
+
|
|
571
|
+
const result = {
|
|
572
|
+
type: fileType,
|
|
573
|
+
filename: file.name,
|
|
574
|
+
fileSize: file.size,
|
|
575
|
+
metadata: {},
|
|
576
|
+
ufrxDoc: null,
|
|
577
|
+
allFeatures: [],
|
|
578
|
+
parameters: [],
|
|
579
|
+
fileRefs: [],
|
|
580
|
+
constraints: [],
|
|
581
|
+
reconstructionGuide: [],
|
|
582
|
+
rawStreams: [],
|
|
583
|
+
error: null
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
const ole2 = parseOLE2(arr);
|
|
588
|
+
result.metadata = { oleVersion: ole2.version, streams: ole2.entries.length };
|
|
589
|
+
|
|
590
|
+
// 1. Parse UFRxDoc (primary feature/metadata source)
|
|
591
|
+
result.ufrxDoc = parseUFRxDoc(ole2);
|
|
592
|
+
|
|
593
|
+
// 2. Deep-scan all streams
|
|
594
|
+
const scanResult = scanAllStreams(ole2);
|
|
595
|
+
|
|
596
|
+
// 3. Merge features from UFRxDoc + stream scan (deduplicate)
|
|
597
|
+
const featureMap = new Map();
|
|
598
|
+
if (result.ufrxDoc?.features) {
|
|
599
|
+
for (const f of result.ufrxDoc.features) featureMap.set(f.type, f);
|
|
600
|
+
}
|
|
601
|
+
for (const f of scanResult.features) {
|
|
602
|
+
if (!featureMap.has(f.type) || f.count > featureMap.get(f.type).count) {
|
|
603
|
+
featureMap.set(f.type, f);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
result.allFeatures = Array.from(featureMap.values());
|
|
607
|
+
|
|
608
|
+
// 4. Merge parameters
|
|
609
|
+
result.parameters = result.ufrxDoc?.parameters || [];
|
|
610
|
+
|
|
611
|
+
// 5. File references
|
|
612
|
+
result.fileRefs = [...new Set([...(result.ufrxDoc?.references || []), ...scanResult.fileRefs])];
|
|
613
|
+
|
|
614
|
+
// 6. Constraints
|
|
615
|
+
result.constraints = scanResult.constraints;
|
|
616
|
+
|
|
617
|
+
// 7. Generate reconstruction guide
|
|
618
|
+
if (isIAM) {
|
|
619
|
+
result.reconstructionGuide = generateAssemblyGuide(result);
|
|
620
|
+
} else {
|
|
621
|
+
result.reconstructionGuide = generateFusion360Guide(result);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// 8. Raw stream info
|
|
625
|
+
result.rawStreams = ole2.getAllStreams().map(e => ({ name: e.name, size: e.size }));
|
|
626
|
+
|
|
627
|
+
} catch (error) {
|
|
628
|
+
result.error = error.message;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ============================================================================
|
|
635
|
+
// UI PANEL (v2 with Reconstruction Guide tab)
|
|
636
|
+
// ============================================================================
|
|
637
|
+
|
|
638
|
+
export function createInventorPanel(onFileLoaded = null) {
|
|
639
|
+
// Remove existing panel
|
|
640
|
+
const existing = document.getElementById('inventor-panel');
|
|
641
|
+
if (existing) existing.remove();
|
|
642
|
+
|
|
643
|
+
const panel = document.createElement('div');
|
|
644
|
+
panel.id = 'inventor-panel';
|
|
645
|
+
panel.style.cssText = `
|
|
646
|
+
position: fixed;
|
|
647
|
+
top: 50%; left: 50%;
|
|
648
|
+
transform: translate(-50%, -50%);
|
|
649
|
+
width: min(92vw, 1100px);
|
|
650
|
+
height: min(92vh, 850px);
|
|
651
|
+
background: #1e1e1e;
|
|
652
|
+
border: 1px solid #3e3e42;
|
|
653
|
+
border-radius: 8px;
|
|
654
|
+
z-index: 600;
|
|
655
|
+
display: flex;
|
|
656
|
+
flex-direction: column;
|
|
657
|
+
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
|
658
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
659
|
+
color: #e0e0e0;
|
|
660
|
+
`;
|
|
661
|
+
|
|
662
|
+
// Header
|
|
663
|
+
const header = document.createElement('div');
|
|
664
|
+
header.style.cssText = `
|
|
665
|
+
padding: 14px 16px;
|
|
666
|
+
border-bottom: 1px solid #3e3e42;
|
|
667
|
+
display: flex;
|
|
668
|
+
justify-content: space-between;
|
|
669
|
+
align-items: center;
|
|
670
|
+
user-select: none;
|
|
671
|
+
cursor: move;
|
|
672
|
+
background: linear-gradient(135deg, #1e1e1e, #252530);
|
|
673
|
+
`;
|
|
674
|
+
header.innerHTML = `
|
|
675
|
+
<div style="display: flex; align-items: center; gap: 10px;">
|
|
676
|
+
<span style="font-size: 20px;">๐ญ</span>
|
|
677
|
+
<div>
|
|
678
|
+
<h2 style="margin: 0; font-size: 15px; color: #e0e0e0;">Inventor โ Fusion 360 / cycleCAD</h2>
|
|
679
|
+
<div style="font-size: 11px; color: #8b949e; margin-top: 2px;">Reverse-engineer native .ipt/.iam files</div>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
`;
|
|
683
|
+
const closeBtn = document.createElement('button');
|
|
684
|
+
closeBtn.textContent = 'โ';
|
|
685
|
+
closeBtn.style.cssText = `background:transparent;border:none;color:#8b949e;font-size:20px;cursor:pointer;padding:4px;`;
|
|
686
|
+
closeBtn.onclick = () => panel.remove();
|
|
687
|
+
header.appendChild(closeBtn);
|
|
688
|
+
panel.appendChild(header);
|
|
689
|
+
|
|
690
|
+
// Content area (drop zone initially)
|
|
691
|
+
const content = document.createElement('div');
|
|
692
|
+
content.style.cssText = `
|
|
693
|
+
flex: 1;
|
|
694
|
+
display: flex;
|
|
695
|
+
align-items: center;
|
|
696
|
+
justify-content: center;
|
|
697
|
+
min-height: 0;
|
|
698
|
+
`;
|
|
699
|
+
|
|
700
|
+
const dropZone = document.createElement('div');
|
|
701
|
+
dropZone.style.cssText = `
|
|
702
|
+
width: calc(100% - 32px);
|
|
703
|
+
height: calc(100% - 32px);
|
|
704
|
+
border: 2px dashed #3e3e42;
|
|
705
|
+
border-radius: 8px;
|
|
706
|
+
display: flex;
|
|
707
|
+
flex-direction: column;
|
|
708
|
+
align-items: center;
|
|
709
|
+
justify-content: center;
|
|
710
|
+
cursor: pointer;
|
|
711
|
+
transition: all 0.2s;
|
|
712
|
+
background: #252526;
|
|
713
|
+
gap: 12px;
|
|
714
|
+
`;
|
|
715
|
+
// Hidden file input (persistent, not dynamic โ works reliably across browsers)
|
|
716
|
+
const fileInput = document.createElement('input');
|
|
717
|
+
fileInput.type = 'file';
|
|
718
|
+
fileInput.accept = '.ipt,.iam';
|
|
719
|
+
fileInput.id = 'inventor-file-input';
|
|
720
|
+
fileInput.style.cssText = 'position:absolute;width:0;height:0;opacity:0;pointer-events:none;';
|
|
721
|
+
fileInput.onchange = async (e) => {
|
|
722
|
+
if (e.target.files.length > 0) await showResults(e.target.files[0], content, panel, onFileLoaded);
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
dropZone.innerHTML = `
|
|
726
|
+
<div style="font-size: 48px;">๐ญ</div>
|
|
727
|
+
<div style="color: #e0e0e0; font-size: 15px; font-weight: 500;">Drop Inventor File Here</div>
|
|
728
|
+
<div style="color: #8b949e; font-size: 12px;">Supports .ipt (Part) and .iam (Assembly)</div>
|
|
729
|
+
`;
|
|
730
|
+
|
|
731
|
+
// Browse button using <label> wrapping hidden input โ reliable click-to-browse
|
|
732
|
+
const browseLabel = document.createElement('label');
|
|
733
|
+
browseLabel.setAttribute('for', 'inventor-file-input');
|
|
734
|
+
browseLabel.style.cssText = `
|
|
735
|
+
display: inline-block;
|
|
736
|
+
margin-top: 8px;
|
|
737
|
+
padding: 8px 24px;
|
|
738
|
+
background: rgba(255,140,0,0.15);
|
|
739
|
+
border: 1px solid rgba(255,140,0,0.4);
|
|
740
|
+
border-radius: 6px;
|
|
741
|
+
color: #ff8c00;
|
|
742
|
+
font-size: 13px;
|
|
743
|
+
font-weight: 500;
|
|
744
|
+
cursor: pointer;
|
|
745
|
+
transition: all 0.2s;
|
|
746
|
+
`;
|
|
747
|
+
browseLabel.textContent = 'Browse Files...';
|
|
748
|
+
browseLabel.onmouseenter = () => { browseLabel.style.background = 'rgba(255,140,0,0.25)'; };
|
|
749
|
+
browseLabel.onmouseleave = () => { browseLabel.style.background = 'rgba(255,140,0,0.15)'; };
|
|
750
|
+
|
|
751
|
+
dropZone.appendChild(fileInput);
|
|
752
|
+
dropZone.appendChild(browseLabel);
|
|
753
|
+
|
|
754
|
+
// Sample DUO files section
|
|
755
|
+
const samplesDiv = document.createElement('div');
|
|
756
|
+
samplesDiv.style.cssText = `
|
|
757
|
+
margin-top: 16px;
|
|
758
|
+
padding-top: 16px;
|
|
759
|
+
border-top: 1px solid #3e3e42;
|
|
760
|
+
text-align: center;
|
|
761
|
+
width: 100%;
|
|
762
|
+
`;
|
|
763
|
+
samplesDiv.innerHTML = `
|
|
764
|
+
<div style="color:#8b949e;font-size:11px;margin-bottom:10px;">DUO Sample Files</div>
|
|
765
|
+
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap;">
|
|
766
|
+
<button class="inv-sample-btn" data-file="Leistenbuerstenblech.ipt" style="padding:6px 14px;background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3);border-radius:5px;color:#58a6ff;font-size:12px;cursor:pointer;">Leistenbuerstenblech.ipt</button>
|
|
767
|
+
<button class="inv-sample-btn" data-file="TraegerHoehe1.ipt" style="padding:6px 14px;background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3);border-radius:5px;color:#58a6ff;font-size:12px;cursor:pointer;">TraegerHoehe1.ipt</button>
|
|
768
|
+
<button class="inv-sample-btn" data-file="Rahmen_Seite.iam" style="padding:6px 14px;background:rgba(88,166,255,0.1);border:1px solid rgba(88,166,255,0.3);border-radius:5px;color:#58a6ff;font-size:12px;cursor:pointer;">Rahmen_Seite.iam</button>
|
|
769
|
+
</div>
|
|
770
|
+
`;
|
|
771
|
+
dropZone.appendChild(samplesDiv);
|
|
772
|
+
|
|
773
|
+
// Sample button click handlers โ fetch from samples/ directory
|
|
774
|
+
samplesDiv.querySelectorAll('.inv-sample-btn').forEach(btn => {
|
|
775
|
+
btn.onmouseenter = () => { btn.style.background = 'rgba(88,166,255,0.2)'; };
|
|
776
|
+
btn.onmouseleave = () => { btn.style.background = 'rgba(88,166,255,0.1)'; };
|
|
777
|
+
btn.onclick = async (e) => {
|
|
778
|
+
e.stopPropagation();
|
|
779
|
+
const fileName = btn.dataset.file;
|
|
780
|
+
btn.textContent = 'Loading...';
|
|
781
|
+
btn.style.opacity = '0.6';
|
|
782
|
+
try {
|
|
783
|
+
const resp = await fetch(`samples/${fileName}`);
|
|
784
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
785
|
+
const blob = await resp.blob();
|
|
786
|
+
const file = new File([blob], fileName);
|
|
787
|
+
await showResults(file, content, panel, onFileLoaded);
|
|
788
|
+
} catch (err) {
|
|
789
|
+
btn.textContent = `Failed: ${err.message}`;
|
|
790
|
+
btn.style.color = '#f85149';
|
|
791
|
+
btn.style.borderColor = 'rgba(248,81,73,0.3)';
|
|
792
|
+
setTimeout(() => {
|
|
793
|
+
btn.textContent = fileName;
|
|
794
|
+
btn.style.color = '#58a6ff';
|
|
795
|
+
btn.style.borderColor = 'rgba(88,166,255,0.3)';
|
|
796
|
+
btn.style.opacity = '1';
|
|
797
|
+
}, 2000);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
dropZone.ondragover = (e) => { e.preventDefault(); dropZone.style.borderColor = '#ff8c00'; dropZone.style.background = '#2a2520'; };
|
|
803
|
+
dropZone.ondragleave = () => { dropZone.style.borderColor = '#3e3e42'; dropZone.style.background = '#252526'; };
|
|
804
|
+
dropZone.ondrop = async (e) => {
|
|
805
|
+
e.preventDefault();
|
|
806
|
+
dropZone.style.borderColor = '#3e3e42';
|
|
807
|
+
const files = Array.from(e.dataTransfer.files).filter(f => /\.(ipt|iam)$/i.test(f.name));
|
|
808
|
+
if (files.length > 0) await showResults(files[0], content, panel, onFileLoaded);
|
|
809
|
+
};
|
|
810
|
+
// Prevent dropZone click from interfering with label/button clicks
|
|
811
|
+
dropZone.style.cursor = 'default';
|
|
812
|
+
|
|
813
|
+
content.appendChild(dropZone);
|
|
814
|
+
panel.appendChild(content);
|
|
815
|
+
|
|
816
|
+
// Draggable
|
|
817
|
+
makeDraggable(panel, header);
|
|
818
|
+
|
|
819
|
+
// Auto-show
|
|
820
|
+
document.body.appendChild(panel);
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
element: panel,
|
|
824
|
+
show: () => document.body.appendChild(panel),
|
|
825
|
+
hide: () => panel.remove(),
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function makeDraggable(el, handle) {
|
|
830
|
+
let ox = 0, oy = 0;
|
|
831
|
+
handle.onmousedown = (e) => {
|
|
832
|
+
if (e.target.tagName === 'BUTTON') return;
|
|
833
|
+
const rect = el.getBoundingClientRect();
|
|
834
|
+
ox = e.clientX - rect.left; oy = e.clientY - rect.top;
|
|
835
|
+
const move = (ev) => { el.style.transform = 'none'; el.style.left = (ev.clientX - ox) + 'px'; el.style.top = (ev.clientY - oy) + 'px'; };
|
|
836
|
+
const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); };
|
|
837
|
+
document.addEventListener('mousemove', move);
|
|
838
|
+
document.addEventListener('mouseup', up);
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
async function showResults(file, container, panel, callback) {
|
|
843
|
+
container.innerHTML = '<div style="color: #ff8c00; font-size: 14px;">โณ Parsing Inventor file...</div>';
|
|
844
|
+
|
|
845
|
+
const data = await parseInventorFile(file);
|
|
846
|
+
|
|
847
|
+
container.innerHTML = '';
|
|
848
|
+
container.style.cssText = 'flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden;';
|
|
849
|
+
|
|
850
|
+
// Tab bar
|
|
851
|
+
const tabBar = document.createElement('div');
|
|
852
|
+
tabBar.style.cssText = 'display:flex;border-bottom:1px solid #3e3e42;background:#1a1a1a;flex-shrink:0;';
|
|
853
|
+
|
|
854
|
+
const isIAM = data.type === 'iam';
|
|
855
|
+
const tabNames = ['๐ Overview', '๐ง Rebuild Guide', '๐ฆ Features', 'โ๏ธ Parameters', ...(isIAM ? ['๐๏ธ Assembly'] : []), '๐ Raw Data'];
|
|
856
|
+
const tabPanels = {};
|
|
857
|
+
|
|
858
|
+
tabNames.forEach((name, i) => {
|
|
859
|
+
const btn = document.createElement('button');
|
|
860
|
+
btn.textContent = name;
|
|
861
|
+
btn.style.cssText = `flex:1;padding:10px 6px;background:transparent;border:none;border-bottom:2px solid transparent;color:#8b949e;cursor:pointer;font-size:11px;white-space:nowrap;`;
|
|
862
|
+
btn.onclick = () => {
|
|
863
|
+
Object.values(tabPanels).forEach(p => p.style.display = 'none');
|
|
864
|
+
tabPanels[name].style.display = 'flex';
|
|
865
|
+
Array.from(tabBar.children).forEach(b => { b.style.borderBottomColor = 'transparent'; b.style.color = '#8b949e'; });
|
|
866
|
+
btn.style.borderBottomColor = '#ff8c00'; btn.style.color = '#e0e0e0';
|
|
867
|
+
};
|
|
868
|
+
if (i === 0) { btn.style.borderBottomColor = '#ff8c00'; btn.style.color = '#e0e0e0'; }
|
|
869
|
+
tabBar.appendChild(btn);
|
|
870
|
+
|
|
871
|
+
const p = document.createElement('div');
|
|
872
|
+
p.style.cssText = `flex:1;overflow-y:auto;padding:16px;display:${i === 0 ? 'flex' : 'none'};flex-direction:column;gap:8px;min-height:0;font-size:12px;`;
|
|
873
|
+
tabPanels[name] = p;
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
container.appendChild(tabBar);
|
|
877
|
+
|
|
878
|
+
// ====== OVERVIEW TAB ======
|
|
879
|
+
const overview = tabPanels['๐ Overview'];
|
|
880
|
+
const partType = data.ufrxDoc?.partType || 'Standard';
|
|
881
|
+
const templateIcon = TEMPLATE_MAP[Object.keys(TEMPLATE_MAP).find(k => partType.includes(k))]?.icon || '๐ฆ';
|
|
882
|
+
overview.innerHTML = `
|
|
883
|
+
<div style="background:#252526;padding:14px;border-radius:6px;border-left:3px solid #ff8c00;">
|
|
884
|
+
<div style="font-size:16px;font-weight:600;margin-bottom:8px;">${templateIcon} ${data.filename}</div>
|
|
885
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;color:#b0b0b0;">
|
|
886
|
+
<div>Type: <strong style="color:#e0e0e0;">${data.type.toUpperCase()} โ ${partType}</strong></div>
|
|
887
|
+
<div>Size: <strong style="color:#e0e0e0;">${(data.fileSize / 1024).toFixed(1)} KB</strong></div>
|
|
888
|
+
<div>OLE Version: <strong style="color:#e0e0e0;">${data.metadata.oleVersion}</strong></div>
|
|
889
|
+
<div>Template: <strong style="color:#e0e0e0;">${data.ufrxDoc?.template || 'โ'}</strong></div>
|
|
890
|
+
<div>Created with: <strong style="color:#e0e0e0;">${data.ufrxDoc?.savedFrom || 'โ'}</strong></div>
|
|
891
|
+
<div>Saved on: <strong style="color:#e0e0e0;">${data.ufrxDoc?.savedOn || 'โ'}</strong></div>
|
|
892
|
+
<div style="grid-column:1/3;">Path: <strong style="color:#e0e0e0;word-break:break-all;font-size:10px;">${data.ufrxDoc?.originalPath || 'โ'}</strong></div>
|
|
893
|
+
</div>
|
|
894
|
+
</div>
|
|
895
|
+
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;">
|
|
896
|
+
<div style="background:#252526;padding:12px;border-radius:6px;text-align:center;">
|
|
897
|
+
<div style="font-size:24px;color:#3fb950;">${data.allFeatures.length}</div>
|
|
898
|
+
<div style="color:#8b949e;font-size:11px;">Features</div>
|
|
899
|
+
</div>
|
|
900
|
+
<div style="background:#252526;padding:12px;border-radius:6px;text-align:center;">
|
|
901
|
+
<div style="font-size:24px;color:#58a6ff;">${data.parameters.length}</div>
|
|
902
|
+
<div style="color:#8b949e;font-size:11px;">Parameters</div>
|
|
903
|
+
</div>
|
|
904
|
+
<div style="background:#252526;padding:12px;border-radius:6px;text-align:center;">
|
|
905
|
+
<div style="font-size:24px;color:#d29922;">${data.fileRefs.length}</div>
|
|
906
|
+
<div style="color:#8b949e;font-size:11px;">File Refs</div>
|
|
907
|
+
</div>
|
|
908
|
+
<div style="background:#252526;padding:12px;border-radius:6px;text-align:center;">
|
|
909
|
+
<div style="font-size:24px;color:#a371f7;">${data.rawStreams.length}</div>
|
|
910
|
+
<div style="color:#8b949e;font-size:11px;">Streams</div>
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
${data.error ? `<div style="background:#3d1f1f;padding:10px;border-radius:6px;color:#f85149;">โ ๏ธ ${data.error}</div>` : ''}
|
|
914
|
+
`;
|
|
915
|
+
|
|
916
|
+
// ====== REBUILD GUIDE TAB ======
|
|
917
|
+
const guideTab = tabPanels['๐ง Rebuild Guide'];
|
|
918
|
+
if (data.reconstructionGuide.length > 0) {
|
|
919
|
+
// Header
|
|
920
|
+
const guideHeader = document.createElement('div');
|
|
921
|
+
guideHeader.style.cssText = 'background:linear-gradient(135deg,#1a2030,#252540);padding:12px;border-radius:6px;margin-bottom:4px;';
|
|
922
|
+
guideHeader.innerHTML = `
|
|
923
|
+
<div style="font-size:14px;font-weight:600;color:#ff8c00;">Step-by-Step Reconstruction</div>
|
|
924
|
+
<div style="font-size:11px;color:#8b949e;margin-top:4px;">Recreate this ${partType} in Fusion 360 (free) or cycleCAD</div>
|
|
925
|
+
`;
|
|
926
|
+
guideTab.appendChild(guideHeader);
|
|
927
|
+
|
|
928
|
+
// Steps
|
|
929
|
+
data.reconstructionGuide.forEach(step => {
|
|
930
|
+
const stepEl = document.createElement('div');
|
|
931
|
+
stepEl.style.cssText = `
|
|
932
|
+
background: #252526;
|
|
933
|
+
padding: 12px;
|
|
934
|
+
border-radius: 6px;
|
|
935
|
+
border-left: 3px solid ${step.color || '#ff8c00'};
|
|
936
|
+
`;
|
|
937
|
+
stepEl.innerHTML = `
|
|
938
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
|
939
|
+
<span style="background:#ff8c00;color:#000;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;">Step ${step.step}</span>
|
|
940
|
+
<strong>${step.action}</strong>
|
|
941
|
+
</div>
|
|
942
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:6px;">
|
|
943
|
+
<div style="background:#1a2520;padding:8px;border-radius:4px;">
|
|
944
|
+
<div style="color:#3fb950;font-size:10px;font-weight:600;margin-bottom:4px;">FUSION 360</div>
|
|
945
|
+
<div style="white-space:pre-wrap;font-size:11px;color:#b0b0b0;">${step.f360}</div>
|
|
946
|
+
</div>
|
|
947
|
+
<div style="background:#1a1a2a;padding:8px;border-radius:4px;">
|
|
948
|
+
<div style="color:#58a6ff;font-size:10px;font-weight:600;margin-bottom:4px;">cycleCAD</div>
|
|
949
|
+
<div style="font-family:monospace;font-size:11px;color:#b0b0b0;">${step.cycleCAD}</div>
|
|
950
|
+
</div>
|
|
951
|
+
</div>
|
|
952
|
+
${step.tip ? `<div style="color:#8b949e;font-size:10px;margin-top:6px;font-style:italic;">๐ก ${step.tip}</div>` : ''}
|
|
953
|
+
`;
|
|
954
|
+
guideTab.appendChild(stepEl);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
// Export button
|
|
958
|
+
const exportGuideBtn = document.createElement('button');
|
|
959
|
+
exportGuideBtn.textContent = '๐ Export Rebuild Guide as HTML';
|
|
960
|
+
exportGuideBtn.style.cssText = 'margin-top:8px;padding:10px;background:#ff8c00;border:none;color:#000;border-radius:6px;cursor:pointer;font-weight:600;font-size:12px;';
|
|
961
|
+
exportGuideBtn.onclick = () => exportGuideHTML(data);
|
|
962
|
+
guideTab.appendChild(exportGuideBtn);
|
|
963
|
+
} else {
|
|
964
|
+
guideTab.innerHTML = '<div style="color:#8b949e;">No reconstruction guide available</div>';
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// ====== FEATURES TAB ======
|
|
968
|
+
const featTab = tabPanels['๐ฆ Features'];
|
|
969
|
+
if (data.allFeatures.length > 0) {
|
|
970
|
+
data.allFeatures.forEach(f => {
|
|
971
|
+
const el = document.createElement('div');
|
|
972
|
+
el.style.cssText = `padding:10px;background:#252526;border-left:3px solid ${f.color};border-radius:4px;`;
|
|
973
|
+
el.innerHTML = `
|
|
974
|
+
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
975
|
+
<strong>${f.icon} ${f.type}</strong>
|
|
976
|
+
<span style="color:#8b949e;font-size:10px;">ร${f.count} | ${f.source}</span>
|
|
977
|
+
</div>
|
|
978
|
+
<div style="margin-top:4px;font-size:11px;color:#8b949e;">
|
|
979
|
+
Fusion 360: <span style="color:#3fb950;">${f.f360}</span> ยท
|
|
980
|
+
cycleCAD: <code style="color:#58a6ff;">${f.cycleCAD}()</code>
|
|
981
|
+
</div>
|
|
982
|
+
`;
|
|
983
|
+
featTab.appendChild(el);
|
|
984
|
+
});
|
|
985
|
+
} else {
|
|
986
|
+
featTab.innerHTML = '<div style="color:#8b949e;">No features detected in binary streams. This file may use compressed geometry.</div>';
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// ====== PARAMETERS TAB ======
|
|
990
|
+
const paramTab = tabPanels['โ๏ธ Parameters'];
|
|
991
|
+
if (data.parameters.length > 0) {
|
|
992
|
+
const table = document.createElement('table');
|
|
993
|
+
table.style.cssText = 'width:100%;border-collapse:collapse;';
|
|
994
|
+
table.innerHTML = `
|
|
995
|
+
<thead><tr style="border-bottom:1px solid #3e3e42;">
|
|
996
|
+
<th style="text-align:left;padding:8px;color:#ff8c00;">Name</th>
|
|
997
|
+
<th style="text-align:right;padding:8px;color:#ff8c00;">ID/Value</th>
|
|
998
|
+
<th style="text-align:left;padding:8px;color:#ff8c00;">Source</th>
|
|
999
|
+
</tr></thead>
|
|
1000
|
+
<tbody>${data.parameters.map(p => `
|
|
1001
|
+
<tr style="border-bottom:1px solid #2a2a2a;">
|
|
1002
|
+
<td style="padding:6px;"><code>${p.name}</code></td>
|
|
1003
|
+
<td style="text-align:right;padding:6px;">${p.id ?? p.value ?? '?'}</td>
|
|
1004
|
+
<td style="padding:6px;color:#8b949e;font-size:10px;">${p.source || 'โ'}</td>
|
|
1005
|
+
</tr>
|
|
1006
|
+
`).join('')}</tbody>
|
|
1007
|
+
`;
|
|
1008
|
+
paramTab.appendChild(table);
|
|
1009
|
+
} else {
|
|
1010
|
+
paramTab.innerHTML = '<div style="color:#8b949e;">No parameters extracted. Parameters may be in compressed streams.</div>';
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// ====== ASSEMBLY TAB (if .iam) ======
|
|
1014
|
+
if (isIAM && tabPanels['๐๏ธ Assembly']) {
|
|
1015
|
+
const asmTab = tabPanels['๐๏ธ Assembly'];
|
|
1016
|
+
if (data.fileRefs.length > 0) {
|
|
1017
|
+
data.fileRefs.forEach(ref => {
|
|
1018
|
+
const el = document.createElement('div');
|
|
1019
|
+
const isAsm = ref.toLowerCase().endsWith('.iam');
|
|
1020
|
+
el.style.cssText = `padding:8px;background:#252526;border-radius:4px;border-left:3px solid ${isAsm ? '#d29922' : '#58a6ff'};`;
|
|
1021
|
+
el.innerHTML = `${isAsm ? '๐๏ธ' : '๐ง'} <strong>${ref}</strong> <span style="color:#8b949e;font-size:10px;">${isAsm ? 'Assembly' : 'Part'}</span>`;
|
|
1022
|
+
asmTab.appendChild(el);
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
if (data.constraints.length > 0) {
|
|
1026
|
+
const conTitle = document.createElement('div');
|
|
1027
|
+
conTitle.style.cssText = 'margin-top:12px;font-weight:600;color:#ff8c00;';
|
|
1028
|
+
conTitle.textContent = 'Constraints โ Fusion 360 Joints:';
|
|
1029
|
+
asmTab.appendChild(conTitle);
|
|
1030
|
+
data.constraints.forEach(c => {
|
|
1031
|
+
const el = document.createElement('div');
|
|
1032
|
+
el.style.cssText = 'padding:6px;background:#252526;border-radius:4px;margin-top:4px;';
|
|
1033
|
+
el.innerHTML = `๐ ${c.type} โ <span style="color:#3fb950;">${c.f360}</span>`;
|
|
1034
|
+
asmTab.appendChild(el);
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// ====== RAW DATA TAB ======
|
|
1040
|
+
const rawTab = tabPanels['๐ Raw Data'];
|
|
1041
|
+
rawTab.innerHTML = `
|
|
1042
|
+
<div style="display:flex;flex-wrap:wrap;gap:6px;">
|
|
1043
|
+
${data.rawStreams.map(s => `
|
|
1044
|
+
<div style="background:#252526;padding:6px 10px;border-radius:4px;font-size:11px;">
|
|
1045
|
+
<strong>${s.name}</strong> <span style="color:#8b949e;">(${(s.size/1024).toFixed(1)} KB)</span>
|
|
1046
|
+
</div>
|
|
1047
|
+
`).join('')}
|
|
1048
|
+
</div>
|
|
1049
|
+
`;
|
|
1050
|
+
|
|
1051
|
+
// Add all tab panels
|
|
1052
|
+
for (const p of Object.values(tabPanels)) {
|
|
1053
|
+
container.appendChild(p);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// Footer with export
|
|
1057
|
+
const footer = document.createElement('div');
|
|
1058
|
+
footer.style.cssText = 'padding:10px 16px;border-top:1px solid #3e3e42;display:flex;gap:8px;justify-content:flex-end;flex-shrink:0;';
|
|
1059
|
+
|
|
1060
|
+
const jsonBtn = document.createElement('button');
|
|
1061
|
+
jsonBtn.textContent = '๐พ Export JSON';
|
|
1062
|
+
jsonBtn.style.cssText = 'padding:6px 14px;background:#3e3e42;border:none;color:#e0e0e0;border-radius:4px;cursor:pointer;font-size:11px;';
|
|
1063
|
+
jsonBtn.onclick = () => {
|
|
1064
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
1065
|
+
const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
|
|
1066
|
+
a.download = `${data.filename}.analysis.json`; a.click();
|
|
1067
|
+
};
|
|
1068
|
+
footer.appendChild(jsonBtn);
|
|
1069
|
+
|
|
1070
|
+
const htmlBtn = document.createElement('button');
|
|
1071
|
+
htmlBtn.textContent = '๐ Export Rebuild Guide';
|
|
1072
|
+
htmlBtn.style.cssText = 'padding:6px 14px;background:#ff8c00;border:none;color:#000;border-radius:4px;cursor:pointer;font-size:11px;font-weight:600;';
|
|
1073
|
+
htmlBtn.onclick = () => exportGuideHTML(data);
|
|
1074
|
+
footer.appendChild(htmlBtn);
|
|
1075
|
+
|
|
1076
|
+
container.appendChild(footer);
|
|
1077
|
+
|
|
1078
|
+
if (callback) callback(data);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function exportGuideHTML(data) {
|
|
1082
|
+
const partType = data.ufrxDoc?.partType || 'Standard';
|
|
1083
|
+
const html = `<!DOCTYPE html>
|
|
1084
|
+
<html lang="en"><head><meta charset="UTF-8"><title>Rebuild Guide: ${data.filename}</title>
|
|
1085
|
+
<style>
|
|
1086
|
+
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 900px; margin: 40px auto; padding: 20px; background: #0d1117; color: #e0e0e0; }
|
|
1087
|
+
h1 { color: #ff8c00; border-bottom: 2px solid #ff8c00; padding-bottom: 10px; }
|
|
1088
|
+
.meta { background: #161b22; padding: 16px; border-radius: 8px; margin-bottom: 20px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
1089
|
+
.step { background: #161b22; padding: 16px; border-radius: 8px; margin-bottom: 12px; border-left: 4px solid #ff8c00; }
|
|
1090
|
+
.step-num { background: #ff8c00; color: #000; padding: 2px 10px; border-radius: 12px; font-weight: 700; font-size: 12px; }
|
|
1091
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
|
|
1092
|
+
.f360 { background: #0d2818; padding: 10px; border-radius: 6px; }
|
|
1093
|
+
.f360 h4 { color: #3fb950; margin: 0 0 6px 0; font-size: 11px; }
|
|
1094
|
+
.cad { background: #0d0d28; padding: 10px; border-radius: 6px; }
|
|
1095
|
+
.cad h4 { color: #58a6ff; margin: 0 0 6px 0; font-size: 11px; }
|
|
1096
|
+
.tip { color: #8b949e; font-size: 11px; margin-top: 8px; font-style: italic; }
|
|
1097
|
+
code { font-family: 'SF Mono', Consolas, monospace; font-size: 12px; }
|
|
1098
|
+
</style></head><body>
|
|
1099
|
+
<h1>๐ญ Rebuild Guide: ${data.filename}</h1>
|
|
1100
|
+
<div class="meta">
|
|
1101
|
+
<div>Type: <strong>${data.type.toUpperCase()} โ ${partType}</strong></div>
|
|
1102
|
+
<div>Features: <strong>${data.allFeatures.length}</strong></div>
|
|
1103
|
+
<div>Parameters: <strong>${data.parameters.length}</strong></div>
|
|
1104
|
+
<div>Created with: <strong>${data.ufrxDoc?.savedFrom || 'โ'}</strong></div>
|
|
1105
|
+
</div>
|
|
1106
|
+
${data.reconstructionGuide.map(s => `
|
|
1107
|
+
<div class="step">
|
|
1108
|
+
<span class="step-num">Step ${s.step}</span> <strong>${s.action}</strong>
|
|
1109
|
+
<div class="grid">
|
|
1110
|
+
<div class="f360"><h4>FUSION 360 (Free)</h4><div style="white-space:pre-wrap;">${s.f360}</div></div>
|
|
1111
|
+
<div class="cad"><h4>cycleCAD (Web)</h4><code>${s.cycleCAD}</code></div>
|
|
1112
|
+
</div>
|
|
1113
|
+
${s.tip ? `<div class="tip">๐ก ${s.tip}</div>` : ''}
|
|
1114
|
+
</div>`).join('')}
|
|
1115
|
+
<footer style="text-align:center;color:#8b949e;margin-top:40px;font-size:11px;">
|
|
1116
|
+
Generated by cycleCAD Inventor Parser ยท ${new Date().toISOString().split('T')[0]}
|
|
1117
|
+
</footer></body></html>`;
|
|
1118
|
+
|
|
1119
|
+
const blob = new Blob([html], { type: 'text/html' });
|
|
1120
|
+
const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
|
|
1121
|
+
a.download = `${data.filename}_rebuild_guide.html`; a.click();
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// ============================================================================
|
|
1125
|
+
// EXPORTS
|
|
1126
|
+
// ============================================================================
|
|
1127
|
+
|
|
1128
|
+
export { parseOLE2, parseUFRxDoc, scanAllStreams, generateFusion360Guide, generateAssemblyGuide };
|
|
1129
|
+
|
|
1130
|
+
export default {
|
|
1131
|
+
parseInventorFile,
|
|
1132
|
+
createInventorPanel,
|
|
1133
|
+
parseOLE2,
|
|
1134
|
+
parseUFRxDoc,
|
|
1135
|
+
scanAllStreams,
|
|
1136
|
+
generateFusion360Guide,
|
|
1137
|
+
generateAssemblyGuide
|
|
1138
|
+
};
|