brep-io-kernel 1.0.34 → 1.0.35
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/dist-kernel/brep-kernel.js +8357 -8091
- package/package.json +1 -1
- package/src/UI/fileManagerWidget.js +497 -77
- package/src/exporters/threeMF.js +170 -12
- package/src/features/assemblyComponent/AssemblyComponentFeature.js +322 -25
- package/src/githubStorage.js +101 -44
- package/src/services/componentLibrary.js +10 -4
|
@@ -72,15 +72,34 @@ export class AssemblyComponentFeature {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
async run(partHistory) {
|
|
75
|
+
try { console.log('[AssemblyComponentFeature] run: begin', { componentName: this.inputParams?.componentName || null, featureID: this.inputParams?.featureID || null }); } catch { }
|
|
75
76
|
const componentData = await this._resolveComponentData();
|
|
76
77
|
if (!componentData || !componentData.bytes || componentData.bytes.length === 0) {
|
|
77
78
|
const hasSelectionIntent = Boolean((this.inputParams && this.inputParams.componentName) || (this.persistentData && this.persistentData.componentData));
|
|
78
79
|
if (hasSelectionIntent) {
|
|
79
80
|
console.warn('[AssemblyComponentFeature] Component payload missing or failed to load.');
|
|
80
81
|
}
|
|
82
|
+
try { console.warn('[AssemblyComponentFeature] run: abort (no component data)'); } catch { }
|
|
81
83
|
return { added: [], removed: [] };
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
try {
|
|
87
|
+
const stats = await this._debugCount3MFTriangles(componentData.bytes);
|
|
88
|
+
if (stats) {
|
|
89
|
+
console.log('[AssemblyComponentFeature] run: 3MF model stats', stats);
|
|
90
|
+
}
|
|
91
|
+
} catch { }
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
console.log('[AssemblyComponentFeature] run: component data', {
|
|
95
|
+
name: componentData.name || '',
|
|
96
|
+
savedAt: componentData.savedAt || null,
|
|
97
|
+
bytes: componentData.bytes?.length || 0,
|
|
98
|
+
hasFeatureInfo: !!componentData.featureInfo,
|
|
99
|
+
hasBrepExtras: !!componentData.featureInfo?.brepExtras,
|
|
100
|
+
});
|
|
101
|
+
} catch { }
|
|
102
|
+
|
|
84
103
|
const featureId = this._sanitizeFeatureId(this.inputParams?.featureID);
|
|
85
104
|
const group = await this._loadThreeMF(componentData.bytes);
|
|
86
105
|
if (!group) {
|
|
@@ -91,6 +110,7 @@ export class AssemblyComponentFeature {
|
|
|
91
110
|
const solids = await this._buildSolidsFromGroup(group, componentData);
|
|
92
111
|
if (!solids.length) {
|
|
93
112
|
console.warn('[AssemblyComponentFeature] No solids recovered from component.');
|
|
113
|
+
try { console.warn('[AssemblyComponentFeature] run: no solids recovered'); } catch { }
|
|
94
114
|
return { added: [], removed: [] };
|
|
95
115
|
}
|
|
96
116
|
|
|
@@ -150,6 +170,7 @@ export class AssemblyComponentFeature {
|
|
|
150
170
|
featureInfo: componentData.featureInfo || null,
|
|
151
171
|
};
|
|
152
172
|
|
|
173
|
+
try { console.log('[AssemblyComponentFeature] run: complete', { componentName, solids: solids.length }); } catch { }
|
|
153
174
|
return { added: [component], removed: [] };
|
|
154
175
|
}
|
|
155
176
|
|
|
@@ -158,12 +179,21 @@ export class AssemblyComponentFeature {
|
|
|
158
179
|
if (persisted && persisted.data3mf) {
|
|
159
180
|
const bytes = base64ToUint8Array(persisted.data3mf);
|
|
160
181
|
let featureInfo = persisted.featureInfo;
|
|
161
|
-
if (!featureInfo || !featureInfo.history) {
|
|
182
|
+
if (!featureInfo || !featureInfo.history || !featureInfo.brepExtras) {
|
|
162
183
|
featureInfo = await this._extractFeatureInfo(bytes);
|
|
163
184
|
}
|
|
164
185
|
if (featureInfo) {
|
|
165
186
|
this.persistentData.componentData.featureInfo = featureInfo;
|
|
166
187
|
}
|
|
188
|
+
try {
|
|
189
|
+
console.log('[AssemblyComponentFeature] _resolveComponentData: using persisted', {
|
|
190
|
+
name: persisted.name || '',
|
|
191
|
+
savedAt: persisted.savedAt || null,
|
|
192
|
+
bytes: bytes?.length || 0,
|
|
193
|
+
hasFeatureInfo: !!featureInfo,
|
|
194
|
+
hasBrepExtras: !!featureInfo?.brepExtras,
|
|
195
|
+
});
|
|
196
|
+
} catch { }
|
|
167
197
|
return {
|
|
168
198
|
name: persisted.name || '',
|
|
169
199
|
savedAt: persisted.savedAt || null,
|
|
@@ -190,6 +220,16 @@ export class AssemblyComponentFeature {
|
|
|
190
220
|
featureInfo,
|
|
191
221
|
};
|
|
192
222
|
|
|
223
|
+
try {
|
|
224
|
+
console.log('[AssemblyComponentFeature] _resolveComponentData: loaded from library', {
|
|
225
|
+
name: record.name || selectedName,
|
|
226
|
+
savedAt: record.savedAt || null,
|
|
227
|
+
bytes: bytes?.length || 0,
|
|
228
|
+
hasFeatureInfo: !!featureInfo,
|
|
229
|
+
hasBrepExtras: !!featureInfo?.brepExtras,
|
|
230
|
+
});
|
|
231
|
+
} catch { }
|
|
232
|
+
|
|
193
233
|
return {
|
|
194
234
|
name: record.name || selectedName,
|
|
195
235
|
savedAt: record.savedAt || null,
|
|
@@ -211,15 +251,44 @@ export class AssemblyComponentFeature {
|
|
|
211
251
|
}
|
|
212
252
|
}
|
|
213
253
|
|
|
254
|
+
async _debugCount3MFTriangles(bytes) {
|
|
255
|
+
try {
|
|
256
|
+
const buffer = this._toArrayBuffer(bytes);
|
|
257
|
+
if (!buffer) return null;
|
|
258
|
+
const zip = await JSZip.loadAsync(buffer);
|
|
259
|
+
const files = {};
|
|
260
|
+
Object.keys(zip.files || {}).forEach(p => { files[p.toLowerCase()] = p; });
|
|
261
|
+
const modelPath = files['3d/3dmodel.model'] || files['/3d/3dmodel.model'];
|
|
262
|
+
const modelFile = modelPath ? zip.file(modelPath) : null;
|
|
263
|
+
if (!modelFile) return { error: 'model-file-missing' };
|
|
264
|
+
const xml = await modelFile.async('string');
|
|
265
|
+
const triCount = (xml.match(/<triangle\b/gi) || []).length;
|
|
266
|
+
const objCount = (xml.match(/<object\b/gi) || []).length;
|
|
267
|
+
return { objects: objCount, triangles: triCount };
|
|
268
|
+
} catch (err) {
|
|
269
|
+
return { error: err?.message || String(err || 'unknown') };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
214
273
|
async _buildSolidsFromGroup(group, componentData) {
|
|
215
274
|
const solids = [];
|
|
216
275
|
const componentName = this.inputParams.componentName || componentData.name || 'Component';
|
|
217
276
|
const facetInfo = componentData.featureInfo?.facets || null;
|
|
218
277
|
const metadataMap = componentData.featureInfo?.metadata || null;
|
|
278
|
+
const brepExtras = componentData?.featureInfo?.brepExtras || null;
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
console.log('[AssemblyComponentFeature] _buildSolidsFromGroup: start', {
|
|
282
|
+
componentName,
|
|
283
|
+
hasFacetInfo: !!facetInfo,
|
|
284
|
+
hasMetadataMap: !!metadataMap,
|
|
285
|
+
hasBrepExtras: !!brepExtras,
|
|
286
|
+
brepExtrasSolids: brepExtras?.solids ? Object.keys(brepExtras.solids).length : 0,
|
|
287
|
+
});
|
|
288
|
+
} catch { }
|
|
219
289
|
|
|
220
290
|
group.updateMatrixWorld(true);
|
|
221
291
|
|
|
222
|
-
const faceNameCounts = new Map();
|
|
223
292
|
const meshes = [];
|
|
224
293
|
group.traverse((obj) => {
|
|
225
294
|
const geom = obj && obj.isMesh ? obj.geometry : null;
|
|
@@ -250,7 +319,9 @@ export class AssemblyComponentFeature {
|
|
|
250
319
|
const groupName = entry.sourceName || '';
|
|
251
320
|
const groupMeshes = entry.meshes;
|
|
252
321
|
const solidName = this._resolveSolidName(groupName, componentName, ++index);
|
|
253
|
-
const
|
|
322
|
+
const extrasForSolid = brepExtras && brepExtras.solids ? brepExtras.solids[solidName] : null;
|
|
323
|
+
const faceNameCounts = new Map();
|
|
324
|
+
const built = this._buildSolidFromMeshes(groupMeshes, faceNameCounts, groupName, extrasForSolid);
|
|
254
325
|
const solid = built?.solid || null;
|
|
255
326
|
const colorHints = built?.colorHints || null;
|
|
256
327
|
if (!solid || !solid._triVerts || solid._triVerts.length === 0) {
|
|
@@ -262,6 +333,19 @@ export class AssemblyComponentFeature {
|
|
|
262
333
|
|
|
263
334
|
solid.name = solidName;
|
|
264
335
|
|
|
336
|
+
if (extrasForSolid) {
|
|
337
|
+
try {
|
|
338
|
+
console.log('[AssemblyComponentFeature] _buildSolidsFromGroup: applying brepExtras', {
|
|
339
|
+
solidName,
|
|
340
|
+
triCount: (solid._triVerts?.length || 0) / 3,
|
|
341
|
+
hasTriFaceIds: !!extrasForSolid.triFaceIdsB64 || (Array.isArray(extrasForSolid.triFaceIds) && extrasForSolid.triFaceIds.length > 0),
|
|
342
|
+
triFaceOrder: extrasForSolid.triFaceOrder || null,
|
|
343
|
+
idToFaceCount: extrasForSolid.idToFaceName ? Object.keys(extrasForSolid.idToFaceName).length : 0,
|
|
344
|
+
});
|
|
345
|
+
} catch { }
|
|
346
|
+
this._applyBrepExtrasToSolid(solid, extrasForSolid);
|
|
347
|
+
}
|
|
348
|
+
|
|
265
349
|
if (colorHints) {
|
|
266
350
|
this._applyColorHintsToSolid(solid, colorHints);
|
|
267
351
|
}
|
|
@@ -341,6 +425,7 @@ export class AssemblyComponentFeature {
|
|
|
341
425
|
}
|
|
342
426
|
}
|
|
343
427
|
|
|
428
|
+
try { console.log('[AssemblyComponentFeature] _buildSolidsFromGroup: complete', { componentName, solids: solids.length }); } catch { }
|
|
344
429
|
return solids;
|
|
345
430
|
}
|
|
346
431
|
|
|
@@ -364,6 +449,116 @@ export class AssemblyComponentFeature {
|
|
|
364
449
|
try { return `#${color.getHexString()}`; } catch { return null; }
|
|
365
450
|
}
|
|
366
451
|
|
|
452
|
+
_decodeTriFaceIds(extras) {
|
|
453
|
+
if (!extras) return null;
|
|
454
|
+
if (Array.isArray(extras.triFaceIds) && extras.triFaceIds.length) return extras.triFaceIds;
|
|
455
|
+
if (extras.triFaceIdsB64 && typeof extras.triFaceIdsB64 === 'string') {
|
|
456
|
+
try {
|
|
457
|
+
const bytes = base64ToUint8Array(extras.triFaceIdsB64);
|
|
458
|
+
if (!bytes || bytes.length < 4) return null;
|
|
459
|
+
const view = new Uint32Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 4));
|
|
460
|
+
return view;
|
|
461
|
+
} catch {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
_applyBrepExtrasToSolid(solid, extras) {
|
|
469
|
+
if (!solid || !extras) return;
|
|
470
|
+
|
|
471
|
+
if (extras.solidMetadata && typeof extras.solidMetadata === 'object') {
|
|
472
|
+
solid.userData = solid.userData || {};
|
|
473
|
+
const existing = solid.userData.metadata && typeof solid.userData.metadata === 'object'
|
|
474
|
+
? solid.userData.metadata
|
|
475
|
+
: {};
|
|
476
|
+
solid.userData.metadata = { ...existing, ...extras.solidMetadata };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (extras.faceMetadata && typeof extras.faceMetadata === 'object') {
|
|
480
|
+
for (const [faceName, meta] of Object.entries(extras.faceMetadata)) {
|
|
481
|
+
if (!faceName || !meta || typeof meta !== 'object') continue;
|
|
482
|
+
try { solid.setFaceMetadata(faceName, meta); } catch { /* ignore */ }
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (extras.edgeMetadata && typeof extras.edgeMetadata === 'object') {
|
|
487
|
+
const merged = new Map(solid._edgeMetadata instanceof Map ? solid._edgeMetadata : []);
|
|
488
|
+
for (const [edgeName, meta] of Object.entries(extras.edgeMetadata)) {
|
|
489
|
+
if (!edgeName || !meta || typeof meta !== 'object') continue;
|
|
490
|
+
merged.set(edgeName, { ...(merged.get(edgeName) || {}), ...meta });
|
|
491
|
+
}
|
|
492
|
+
solid._edgeMetadata = merged;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (Array.isArray(extras.auxEdges) && extras.auxEdges.length) {
|
|
496
|
+
const cleaned = [];
|
|
497
|
+
for (const aux of extras.auxEdges) {
|
|
498
|
+
const pts = Array.isArray(aux?.points)
|
|
499
|
+
? aux.points.filter((p) => Array.isArray(p) && p.length === 3)
|
|
500
|
+
: [];
|
|
501
|
+
if (pts.length < 2) continue;
|
|
502
|
+
cleaned.push({
|
|
503
|
+
name: aux?.name || 'EDGE',
|
|
504
|
+
points: pts.map((p) => [p[0], p[1], p[2]]),
|
|
505
|
+
closedLoop: !!aux?.closedLoop,
|
|
506
|
+
polylineWorld: !!aux?.polylineWorld,
|
|
507
|
+
materialKey: aux?.materialKey || undefined,
|
|
508
|
+
centerline: !!aux?.centerline,
|
|
509
|
+
faceA: typeof aux?.faceA === 'string' ? aux.faceA : undefined,
|
|
510
|
+
faceB: typeof aux?.faceB === 'string' ? aux.faceB : undefined,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
solid._auxEdges = cleaned;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// If we have per-triangle face IDs ordered to match 3MF material grouping,
|
|
517
|
+
// reapply them to guarantee face names survive loader reordering.
|
|
518
|
+
try {
|
|
519
|
+
const faceIds = this._decodeTriFaceIds(extras);
|
|
520
|
+
const idToFace = extras.idToFaceName && typeof extras.idToFaceName === 'object'
|
|
521
|
+
? extras.idToFaceName
|
|
522
|
+
: null;
|
|
523
|
+
const triCount = (Array.isArray(solid._triVerts) ? solid._triVerts.length : 0) / 3;
|
|
524
|
+
const ordered = extras.triFaceOrder === 'material';
|
|
525
|
+
try {
|
|
526
|
+
console.log('[AssemblyComponentFeature] _applyBrepExtrasToSolid: tri mapping', {
|
|
527
|
+
solidName: solid.name || '',
|
|
528
|
+
triCount,
|
|
529
|
+
faceIdsCount: faceIds?.length || 0,
|
|
530
|
+
ordered,
|
|
531
|
+
idToFaceCount: idToFace ? Object.keys(idToFace).length : 0,
|
|
532
|
+
});
|
|
533
|
+
} catch { }
|
|
534
|
+
if (ordered && idToFace && faceIds && faceIds.length === triCount && triCount > 0) {
|
|
535
|
+
solid._triIDs = Array.from(faceIds);
|
|
536
|
+
const idToName = new Map();
|
|
537
|
+
const nameToId = new Map();
|
|
538
|
+
for (const [rawId, rawName] of Object.entries(idToFace)) {
|
|
539
|
+
const idNum = Number(rawId);
|
|
540
|
+
if (!Number.isFinite(idNum)) continue;
|
|
541
|
+
const name = String(rawName || '');
|
|
542
|
+
if (!name) continue;
|
|
543
|
+
idToName.set(idNum, name);
|
|
544
|
+
nameToId.set(name, idNum);
|
|
545
|
+
}
|
|
546
|
+
if (idToName.size) {
|
|
547
|
+
solid._idToFaceName = idToName;
|
|
548
|
+
solid._faceNameToID = nameToId;
|
|
549
|
+
}
|
|
550
|
+
solid._dirty = true;
|
|
551
|
+
solid._faceIndex = null;
|
|
552
|
+
try {
|
|
553
|
+
console.log('[AssemblyComponentFeature] _applyBrepExtrasToSolid: applied tri IDs', {
|
|
554
|
+
solidName: solid.name || '',
|
|
555
|
+
idToFaceCount: idToName.size,
|
|
556
|
+
});
|
|
557
|
+
} catch { }
|
|
558
|
+
}
|
|
559
|
+
} catch { /* ignore reapply failures */ }
|
|
560
|
+
}
|
|
561
|
+
|
|
367
562
|
_hasColorEntry(metadata, keys) {
|
|
368
563
|
if (!metadata || typeof metadata !== 'object') return false;
|
|
369
564
|
for (const key of keys) {
|
|
@@ -376,7 +571,7 @@ export class AssemblyComponentFeature {
|
|
|
376
571
|
return false;
|
|
377
572
|
}
|
|
378
573
|
|
|
379
|
-
_appendMeshToSolid(solid, mesh, faceNameCounts) {
|
|
574
|
+
_appendMeshToSolid(solid, mesh, faceNameCounts, options = {}) {
|
|
380
575
|
const geometry = mesh?.geometry;
|
|
381
576
|
const posAttr = geometry?.getAttribute?.('position');
|
|
382
577
|
if (!solid || !geometry || !posAttr || posAttr.count < 3) return null;
|
|
@@ -388,14 +583,31 @@ export class AssemblyComponentFeature {
|
|
|
388
583
|
const indexAttr = typeof geometry.getIndex === 'function' ? geometry.getIndex() : null;
|
|
389
584
|
const materialName = this._getMaterialName(mesh.material);
|
|
390
585
|
const baseFaceName = this._safeName(materialName || mesh.name || `FACE_${faceNameCounts.size + 1}`);
|
|
391
|
-
const
|
|
586
|
+
const preferExact = Array.isArray(options.triFaceIds) || options.triFaceIds instanceof Uint32Array;
|
|
587
|
+
const idToFaceName = options.idToFaceName || null;
|
|
392
588
|
|
|
393
589
|
const a = new THREE.Vector3();
|
|
394
590
|
const b = new THREE.Vector3();
|
|
395
591
|
const c = new THREE.Vector3();
|
|
396
592
|
|
|
397
|
-
|
|
398
|
-
const
|
|
593
|
+
const materialArray = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
594
|
+
const triCount = indexAttr && indexAttr.count >= 3
|
|
595
|
+
? Math.floor(indexAttr.count / 3)
|
|
596
|
+
: Math.floor(posAttr.count / 3);
|
|
597
|
+
const materialIndexByTri = new Array(triCount).fill(0);
|
|
598
|
+
if (Array.isArray(geometry.groups) && geometry.groups.length) {
|
|
599
|
+
for (const group of geometry.groups) {
|
|
600
|
+
const start = Math.max(0, group?.start || 0);
|
|
601
|
+
const count = Math.max(0, group?.count || 0);
|
|
602
|
+
const matIdx = Number.isFinite(group?.materialIndex) ? group.materialIndex : 0;
|
|
603
|
+
const triStart = Math.floor(start / 3);
|
|
604
|
+
const triEnd = Math.floor((start + count) / 3);
|
|
605
|
+
for (let t = triStart; t < triEnd && t < triCount; t++) materialIndexByTri[t] = matIdx;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const faceInfosByName = new Map();
|
|
610
|
+
const writeTriangle = (ia, ib, ic, faceName) => {
|
|
399
611
|
if (!Number.isFinite(ia) || !Number.isFinite(ib) || !Number.isFinite(ic)) return;
|
|
400
612
|
if (ia < 0 || ib < 0 || ic < 0) return;
|
|
401
613
|
if (ia >= posAttr.count || ib >= posAttr.count || ic >= posAttr.count) return;
|
|
@@ -403,38 +615,85 @@ export class AssemblyComponentFeature {
|
|
|
403
615
|
b.fromBufferAttribute(posAttr, ib).applyMatrix4(matrixWorld);
|
|
404
616
|
c.fromBufferAttribute(posAttr, ic).applyMatrix4(matrixWorld);
|
|
405
617
|
solid.addTriangle(faceName, [a.x, a.y, a.z], [b.x, b.y, b.z], [c.x, c.y, c.z]);
|
|
406
|
-
added++;
|
|
407
618
|
};
|
|
408
619
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
620
|
+
const fallbackFaceNames = new Map();
|
|
621
|
+
const resolveFallbackFaceName = (triIndex) => {
|
|
622
|
+
const matIdx = materialIndexByTri[triIndex] || 0;
|
|
623
|
+
if (!fallbackFaceNames.has(matIdx)) {
|
|
624
|
+
const mat = materialArray && materialArray.length ? materialArray[matIdx] : null;
|
|
625
|
+
const matName = this._getMaterialName(mat);
|
|
626
|
+
const base = this._safeName(matName || baseFaceName);
|
|
627
|
+
const name = faceNameCounts ? this._uniqueName(faceNameCounts, base) : base;
|
|
628
|
+
fallbackFaceNames.set(matIdx, name);
|
|
629
|
+
}
|
|
630
|
+
return fallbackFaceNames.get(matIdx);
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
const ids = options.triFaceIds;
|
|
634
|
+
const triOffset = options.triOffset || 0;
|
|
635
|
+
const hasExact = !!(preferExact && idToFaceName && ids && (triOffset + triCount) <= ids.length);
|
|
636
|
+
const resolveFaceName = (triIndex) => {
|
|
637
|
+
if (hasExact) {
|
|
638
|
+
const idx = triIndex + triOffset;
|
|
639
|
+
const fid = ids[idx];
|
|
640
|
+
const raw = idToFaceName[fid] ?? idToFaceName[String(fid)] ?? null;
|
|
641
|
+
if (raw) return String(raw);
|
|
642
|
+
return `FACE_${fid}`;
|
|
414
643
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
644
|
+
return resolveFallbackFaceName(triIndex);
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
for (let i = 0; i < triCount; i++) {
|
|
648
|
+
const faceName = resolveFaceName(i);
|
|
649
|
+
if (!faceName) continue;
|
|
650
|
+
if (indexAttr && indexAttr.count >= 3) {
|
|
418
651
|
const base = i * 3;
|
|
419
|
-
writeTriangle(base + 0, base + 1, base + 2);
|
|
652
|
+
writeTriangle(indexAttr.getX(base + 0), indexAttr.getX(base + 1), indexAttr.getX(base + 2), faceName);
|
|
653
|
+
} else {
|
|
654
|
+
const base = i * 3;
|
|
655
|
+
writeTriangle(base + 0, base + 1, base + 2, faceName);
|
|
656
|
+
}
|
|
657
|
+
if (!faceInfosByName.has(faceName)) {
|
|
658
|
+
const mat = materialArray && materialArray.length ? materialArray[materialIndexByTri[i] || 0] : null;
|
|
659
|
+
faceInfosByName.set(faceName, {
|
|
660
|
+
faceName,
|
|
661
|
+
materialName: this._getMaterialName(mat) || materialName,
|
|
662
|
+
colorHex: this._getMaterialColorHex(mat || mesh.material),
|
|
663
|
+
});
|
|
420
664
|
}
|
|
421
665
|
}
|
|
422
666
|
|
|
423
|
-
if (
|
|
667
|
+
if (solid._triVerts.length === 0) return null;
|
|
424
668
|
return {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
colorHex: this._getMaterialColorHex(mesh.material),
|
|
669
|
+
faceInfos: Array.from(faceInfosByName.values()),
|
|
670
|
+
triCount,
|
|
428
671
|
};
|
|
429
672
|
}
|
|
430
673
|
|
|
431
|
-
_buildSolidFromMeshes(meshes, faceNameCounts, sourceName) {
|
|
674
|
+
_buildSolidFromMeshes(meshes, faceNameCounts, sourceName, extras = null) {
|
|
432
675
|
if (!Array.isArray(meshes) || meshes.length === 0) return null;
|
|
433
676
|
const solid = new BREP.Solid();
|
|
434
677
|
const faceInfos = [];
|
|
678
|
+
const triFaceIds = this._decodeTriFaceIds(extras);
|
|
679
|
+
const idToFaceName = (extras && extras.idToFaceName && typeof extras.idToFaceName === 'object')
|
|
680
|
+
? extras.idToFaceName
|
|
681
|
+
: null;
|
|
682
|
+
let triOffset = 0;
|
|
435
683
|
for (const mesh of meshes) {
|
|
436
|
-
const info = this._appendMeshToSolid(solid, mesh, faceNameCounts
|
|
437
|
-
|
|
684
|
+
const info = this._appendMeshToSolid(solid, mesh, faceNameCounts, {
|
|
685
|
+
triFaceIds,
|
|
686
|
+
triOffset,
|
|
687
|
+
idToFaceName,
|
|
688
|
+
});
|
|
689
|
+
if (info && Array.isArray(info.faceInfos)) {
|
|
690
|
+
faceInfos.push(...info.faceInfos);
|
|
691
|
+
} else if (info) {
|
|
692
|
+
faceInfos.push(info);
|
|
693
|
+
}
|
|
694
|
+
if (info && Number.isFinite(info.triCount)) {
|
|
695
|
+
triOffset += info.triCount;
|
|
696
|
+
}
|
|
438
697
|
}
|
|
439
698
|
if (!solid._triVerts || solid._triVerts.length === 0) return null;
|
|
440
699
|
const colorHints = this._deriveColorHints(faceInfos, sourceName);
|
|
@@ -445,11 +704,18 @@ export class AssemblyComponentFeature {
|
|
|
445
704
|
const faceColors = new Map();
|
|
446
705
|
let solidColor = null;
|
|
447
706
|
const solidMaterialName = sourceName ? `${sourceName}_SOLID` : '';
|
|
707
|
+
const defaultMaterialName = sourceName ? `${sourceName}_DEFAULT` : '';
|
|
708
|
+
const isDefaultMaterial = (name) => {
|
|
709
|
+
if (!name || typeof name !== 'string') return false;
|
|
710
|
+
if (defaultMaterialName && name === defaultMaterialName) return true;
|
|
711
|
+
return name.endsWith('_DEFAULT');
|
|
712
|
+
};
|
|
448
713
|
|
|
449
714
|
if (Array.isArray(faceInfos)) {
|
|
450
715
|
for (const info of faceInfos) {
|
|
451
716
|
const hex = info?.colorHex;
|
|
452
717
|
if (!hex) continue;
|
|
718
|
+
if (isDefaultMaterial(info.materialName)) continue;
|
|
453
719
|
if (solidMaterialName && info.materialName === solidMaterialName && !solidColor) {
|
|
454
720
|
solidColor = hex;
|
|
455
721
|
continue;
|
|
@@ -700,6 +966,23 @@ export class AssemblyComponentFeature {
|
|
|
700
966
|
solid._faceMetadata = renamedMetadata;
|
|
701
967
|
}
|
|
702
968
|
|
|
969
|
+
const edgeMetadata = solid._edgeMetadata instanceof Map ? solid._edgeMetadata : null;
|
|
970
|
+
if (edgeMetadata && edgeMetadata.size) {
|
|
971
|
+
const renamedEdges = new Map();
|
|
972
|
+
for (const [edgeName, metadata] of edgeMetadata.entries()) {
|
|
973
|
+
if (!edgeName || typeof edgeName !== 'string') continue;
|
|
974
|
+
let renamed = edgeName;
|
|
975
|
+
if (edgeName.includes('|')) {
|
|
976
|
+
const parts = edgeName.split('|').map((p) => this._withFeaturePrefix(id, p, p));
|
|
977
|
+
renamed = parts.join('|');
|
|
978
|
+
} else {
|
|
979
|
+
renamed = this._withFeaturePrefix(id, edgeName, edgeName);
|
|
980
|
+
}
|
|
981
|
+
renamedEdges.set(renamed, metadata);
|
|
982
|
+
}
|
|
983
|
+
solid._edgeMetadata = renamedEdges;
|
|
984
|
+
}
|
|
985
|
+
|
|
703
986
|
if (Array.isArray(solid._auxEdges) && solid._auxEdges.length) {
|
|
704
987
|
for (const aux of solid._auxEdges) {
|
|
705
988
|
if (!aux) continue;
|
|
@@ -748,6 +1031,18 @@ export class AssemblyComponentFeature {
|
|
|
748
1031
|
if (!buffer) return null;
|
|
749
1032
|
const zip = await JSZip.loadAsync(buffer);
|
|
750
1033
|
const candidates = ['Metadata/featureHistory.json', 'metadata/featurehistory.json'];
|
|
1034
|
+
const extrasCandidates = ['Metadata/brepExtras.json', 'metadata/brepextras.json'];
|
|
1035
|
+
let brepExtras = null;
|
|
1036
|
+
for (const path of extrasCandidates) {
|
|
1037
|
+
const file = zip.file(path);
|
|
1038
|
+
if (!file) continue;
|
|
1039
|
+
try {
|
|
1040
|
+
const text = await file.async('string');
|
|
1041
|
+
const parsed = JSON.parse(text);
|
|
1042
|
+
if (parsed && typeof parsed === 'object') brepExtras = parsed;
|
|
1043
|
+
} catch { /* ignore extras parse errors */ }
|
|
1044
|
+
if (brepExtras) break;
|
|
1045
|
+
}
|
|
751
1046
|
for (const path of candidates) {
|
|
752
1047
|
const file = zip.file(path);
|
|
753
1048
|
if (!file) continue;
|
|
@@ -759,11 +1054,13 @@ export class AssemblyComponentFeature {
|
|
|
759
1054
|
facets: parsed?.facets || null,
|
|
760
1055
|
history: parsed || null,
|
|
761
1056
|
historyString: text,
|
|
1057
|
+
brepExtras,
|
|
762
1058
|
};
|
|
763
1059
|
} catch {
|
|
764
|
-
return null;
|
|
1060
|
+
return brepExtras ? { brepExtras } : null;
|
|
765
1061
|
}
|
|
766
1062
|
}
|
|
1063
|
+
if (brepExtras) return { brepExtras };
|
|
767
1064
|
} catch (err) {
|
|
768
1065
|
console.warn('[AssemblyComponentFeature] Failed to extract feature history from component:', err);
|
|
769
1066
|
}
|