altium-toolkit 1.0.8 → 1.0.10
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/README.md +18 -6
- package/docs/api.md +78 -16
- package/docs/model-format.md +229 -8
- package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +76 -0
- package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +53 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1826 -110
- package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +86 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
- package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
- package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
- package/docs/testing.md +9 -3
- package/package.json +1 -1
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumLayoutParser.mjs +104 -8
- package/src/core/altium/AltiumParser.mjs +196 -45
- package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
- package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
- package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
- package/src/core/altium/IntLibModelParser.mjs +240 -0
- package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
- package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
- package/src/core/altium/LibrarySearchIndex.mjs +215 -0
- package/src/core/altium/NormalizedModelSchema.mjs +36 -0
- package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
- package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
- package/src/core/altium/PcbDefaultsParser.mjs +171 -0
- package/src/core/altium/PcbDimensionParser.mjs +229 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
- package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
- package/src/core/altium/PcbLibModelParser.mjs +235 -14
- package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
- package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
- package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
- package/src/core/altium/PcbModelParser.mjs +495 -32
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
- package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
- package/src/core/altium/PcbPadStackParser.mjs +229 -2
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +224 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
- package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +76 -3
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
- package/src/core/altium/PcbRuleParser.mjs +354 -33
- package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
- package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
- package/src/core/altium/PcbStatisticsBuilder.mjs +541 -0
- package/src/core/altium/PcbStreamExtractor.mjs +111 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
- package/src/core/altium/PcbUnionParser.mjs +307 -0
- package/src/core/altium/PcbViaStackParser.mjs +98 -10
- package/src/core/altium/PcbViaStructureParser.mjs +335 -0
- package/src/core/altium/PrintableTextDecoder.mjs +53 -3
- package/src/core/altium/PrjPcbModelParser.mjs +281 -7
- package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +492 -0
- package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +503 -0
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
- package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
- package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
- package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
- package/src/core/altium/SchematicHarnessParser.mjs +302 -0
- package/src/core/altium/SchematicImageParser.mjs +474 -3
- package/src/core/altium/SchematicImplementationParser.mjs +518 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
- package/src/core/altium/SchematicPinParser.mjs +84 -1
- package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
- package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
- package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
- package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
- package/src/core/altium/SchematicTemplateParser.mjs +256 -0
- package/src/core/altium/SchematicTextParser.mjs +123 -0
- package/src/core/altium/SvgModelCrossLinkValidator.mjs +402 -0
- package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
- package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/core/ole/OleCompoundDocument.mjs +20 -0
- package/src/parser.mjs +35 -0
- package/src/styles/altium-renderers.css +19 -0
- package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
- package/src/ui/PcbInteractionIndex.mjs +9 -4
- package/src/ui/PcbScene3dBuilder.mjs +137 -3
- package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
- package/src/ui/PcbSvgRenderer.mjs +1252 -34
- package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
- package/src/ui/SchematicNoteRenderer.mjs +9 -2
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
- package/src/ui/SchematicShapeRenderer.mjs +362 -0
- package/src/ui/SchematicSvgRenderer.mjs +1442 -92
- package/src/ui/SchematicTypography.mjs +48 -5
- package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
|
@@ -18,7 +18,7 @@ export class PcbScene3dBuilder {
|
|
|
18
18
|
static #DENSE_OVERLAY_MIN_REGION_AREA_RATIO = 0.2
|
|
19
19
|
static #DENSE_OVERLAY_MIN_TRACK_COUNT = 250
|
|
20
20
|
static #DENSE_OVERLAY_KNOCKOUT_COLOR = 0x2f6a2c
|
|
21
|
-
static #PRECISE_BODY_MATCH_TOLERANCE_MIL =
|
|
21
|
+
static #PRECISE_BODY_MATCH_TOLERANCE_MIL = 20
|
|
22
22
|
static #UNMATCHED_BODY_OVERHANG_RATIO = 0.25
|
|
23
23
|
static #UNMATCHED_BODY_MIN_OVERHANG_MIL = 150
|
|
24
24
|
static #UNMATCHED_BODY_MAX_OVERHANG_MIL = 600
|
|
@@ -27,8 +27,8 @@ export class PcbScene3dBuilder {
|
|
|
27
27
|
/**
|
|
28
28
|
* Builds a scene description for host 3D renderers.
|
|
29
29
|
* @param {{ pcb?: { boardOutline?: { widthMil?: number, heightMil?: number, minX?: number, minY?: number, segments?: Array<Record<string, number | string>> }, primitiveLayers?: { layerId: number, name: string }[], pads?: { x: number, y: number, sizeTopX?: number, sizeTopY?: number, sizeMidX?: number, sizeMidY?: number, sizeBottomX?: number, sizeBottomY?: number }[], tracks?: any[], arcs?: any[], fills?: any[], vias?: any[], polygons?: any[], embeddedModels?: any[], componentBodies?: { modelId?: string, checksum?: number | null, embedded?: boolean, name?: string, identifier?: string, positionMil?: { x?: number, y?: number }, rotationDeg?: number, modelRotationDeg?: { x?: number, y?: number, z?: number }, dzMil?: number }[], components?: { designator: string, x: number, y: number, layer?: string, pattern?: string, rotation?: number, height?: number | null, source?: string, modelPath?: string }[] } }} documentModel
|
|
30
|
-
* @param {{ modelRegistry?: { resolveComponentModel: (component: any) => { name: string, relativePath: string, format: string } | null, resolveComponentBodyModel?: (componentBody: any) => { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } | null } | null, boardThicknessMil?: number }} [options]
|
|
31
|
-
* @returns {{ board: { widthMil: number, heightMil: number, thicknessMil: number, minX: number, minY: number, centerX: number, centerY: number, segments: Array<Record<string, number | string>> }, components: { designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, boardPositionMil: { x: number, y: number, z: number }, pattern: string, source: string, body: { family: string, sizeMil: { width: number, depth: number, height: number } }, externalModel: { name: string, relativePath: string, format: string } | null }[], externalPlacements: { designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, bodyRotationDeg: number, modelTransform: { rotationDeg: { x: number, y: number, z: number }, dzMil: number }, externalModel: { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } }[], detail: { pads: any[], tracks: any[], arcs: any[], fills: any[], vias: any[], polygons: any[], silkscreen: { top: { fills: any[], tracks: any[], arcs: any[], texts: any[], fillColor?: number, strokeColor?: number }, bottom: { fills: any[], tracks: any[], arcs: any[], texts: any[], fillColor?: number, strokeColor?: number } } } }}
|
|
30
|
+
* @param {{ modelRegistry?: { resolveComponentModel: (component: any) => { name: string, relativePath: string, format: string } | null, resolveComponentBodyModel?: (componentBody: any) => { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } | null, resolveBoardAssemblyModel?: (documentModel: any) => { origin: string, name: string, format: string, file?: File | Blob | null, relativePath?: string } | null } | null, boardThicknessMil?: number }} [options]
|
|
31
|
+
* @returns {{ board: { widthMil: number, heightMil: number, thicknessMil: number, minX: number, minY: number, centerX: number, centerY: number, segments: Array<Record<string, number | string>> }, boardAssemblyModel: { origin: string, name: string, format: string, file?: File | Blob | null, relativePath?: string } | null, components: { designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, boardPositionMil: { x: number, y: number, z: number }, pattern: string, source: string, body: { family: string, sizeMil: { width: number, depth: number, height: number } }, externalModel: { name: string, relativePath: string, format: string } | null }[], externalPlacements: { designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, bodyRotationDeg: number, modelTransform: { rotationDeg: { x: number, y: number, z: number }, dzMil: number }, externalModel: { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } }[], detail: { pads: any[], tracks: any[], arcs: any[], fills: any[], vias: any[], polygons: any[], silkscreen: { top: { fills: any[], tracks: any[], arcs: any[], texts: any[], fillColor?: number, strokeColor?: number }, bottom: { fills: any[], tracks: any[], arcs: any[], texts: any[], fillColor?: number, strokeColor?: number } } } }}
|
|
32
32
|
*/
|
|
33
33
|
static build(documentModel, options = {}) {
|
|
34
34
|
const pcb = documentModel?.pcb || {}
|
|
@@ -63,6 +63,10 @@ export class PcbScene3dBuilder {
|
|
|
63
63
|
centerY:
|
|
64
64
|
Number(boardOutline.minY || 0) +
|
|
65
65
|
Number(boardOutline.heightMil || 0) / 2,
|
|
66
|
+
surfaceColor: Number.isInteger(appearance3d.solderMaskTopColor)
|
|
67
|
+
? appearance3d.solderMaskTopColor
|
|
68
|
+
: appearance3d.solderMaskBottomColor,
|
|
69
|
+
edgeColor: appearance3d.boardCoreColor,
|
|
66
70
|
segments: Array.isArray(boardOutline.segments)
|
|
67
71
|
? boardOutline.segments
|
|
68
72
|
: []
|
|
@@ -106,6 +110,9 @@ export class PcbScene3dBuilder {
|
|
|
106
110
|
const sceneDescription = {
|
|
107
111
|
sourceFormat: 'altium',
|
|
108
112
|
board,
|
|
113
|
+
boardAssemblyModel:
|
|
114
|
+
modelRegistry?.resolveBoardAssemblyModel?.(documentModel) ||
|
|
115
|
+
null,
|
|
109
116
|
components: components.map((component) =>
|
|
110
117
|
PcbScene3dBuilder.#buildComponent(
|
|
111
118
|
component,
|
|
@@ -121,6 +128,7 @@ export class PcbScene3dBuilder {
|
|
|
121
128
|
componentBody,
|
|
122
129
|
bodyMatches[index],
|
|
123
130
|
components,
|
|
131
|
+
pads,
|
|
124
132
|
board,
|
|
125
133
|
thicknessMil,
|
|
126
134
|
modelRegistry
|
|
@@ -208,6 +216,7 @@ export class PcbScene3dBuilder {
|
|
|
208
216
|
* @param {{ modelId?: string, checksum?: number | null, embedded?: boolean, name?: string, identifier?: string, layer?: string, positionMil?: { x?: number, y?: number }, rotationDeg?: number, modelRotationDeg?: { x?: number, y?: number, z?: number }, dzMil?: number }} componentBody
|
|
209
217
|
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, rotation?: number, height?: number | null } | null} matchedComponent
|
|
210
218
|
* @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, source?: string, modelPath?: string }[]} components
|
|
219
|
+
* @param {{ x: number, y: number, sizeTopX?: number, sizeTopY?: number, sizeMidX?: number, sizeMidY?: number, sizeBottomX?: number, sizeBottomY?: number }[]} pads
|
|
211
220
|
* @param {{ centerX: number, centerY: number }} board
|
|
212
221
|
* @param {number} thicknessMil
|
|
213
222
|
* @param {{ resolveComponentBodyModel?: (componentBody: any) => { origin: string, name: string, format: string, payloadText?: string, sourceStream?: string, relativePath?: string } | null } | null} modelRegistry
|
|
@@ -217,6 +226,7 @@ export class PcbScene3dBuilder {
|
|
|
217
226
|
componentBody,
|
|
218
227
|
matchedComponent,
|
|
219
228
|
components,
|
|
229
|
+
pads,
|
|
220
230
|
board,
|
|
221
231
|
thicknessMil,
|
|
222
232
|
modelRegistry
|
|
@@ -275,10 +285,134 @@ export class PcbScene3dBuilder {
|
|
|
275
285
|
rotationDeg: modelRotation,
|
|
276
286
|
dzMil: Number(componentBody.dzMil || 0)
|
|
277
287
|
},
|
|
288
|
+
projection: PcbScene3dBuilder.#resolveProjectionDiagnostics(
|
|
289
|
+
componentBody,
|
|
290
|
+
matchedComponent,
|
|
291
|
+
pads,
|
|
292
|
+
resolvedModel
|
|
293
|
+
),
|
|
278
294
|
externalModel: resolvedModel
|
|
279
295
|
}
|
|
280
296
|
}
|
|
281
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Explains which footprint projection source informed one external model.
|
|
300
|
+
* @param {object} componentBody Normalized component body row.
|
|
301
|
+
* @param {{ x: number, y: number, height?: number | null } | null} matchedComponent Matched component.
|
|
302
|
+
* @param {object[]} pads Normalized pad rows.
|
|
303
|
+
* @param {object | null} resolvedModel Resolved model metadata.
|
|
304
|
+
* @returns {{ source: string, reason: string, boundsMil: { width: number, depth: number, height: number } }}
|
|
305
|
+
*/
|
|
306
|
+
static #resolveProjectionDiagnostics(
|
|
307
|
+
componentBody,
|
|
308
|
+
matchedComponent,
|
|
309
|
+
pads,
|
|
310
|
+
resolvedModel
|
|
311
|
+
) {
|
|
312
|
+
const authoredBounds = PcbScene3dBuilder.#firstBounds([
|
|
313
|
+
componentBody?.projectionOverrideMil,
|
|
314
|
+
componentBody?.projectionOverride?.boundsMil,
|
|
315
|
+
componentBody?.projectionBoundsMil
|
|
316
|
+
])
|
|
317
|
+
if (authoredBounds) {
|
|
318
|
+
return {
|
|
319
|
+
source: 'authored-override',
|
|
320
|
+
reason: 'Component body carried an explicit projection override.',
|
|
321
|
+
boundsMil: authoredBounds
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const modelBounds = PcbScene3dBuilder.#firstBounds([
|
|
326
|
+
componentBody?.modelBoundsMil,
|
|
327
|
+
resolvedModel?.boundsMil
|
|
328
|
+
])
|
|
329
|
+
if (modelBounds) {
|
|
330
|
+
return {
|
|
331
|
+
source: 'model-bounds',
|
|
332
|
+
reason: 'Resolved 3D model bounds were available.',
|
|
333
|
+
boundsMil: modelBounds
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (matchedComponent) {
|
|
338
|
+
const padSpan = PcbScene3dBuilder.#resolvePadSpan(
|
|
339
|
+
matchedComponent,
|
|
340
|
+
pads
|
|
341
|
+
)
|
|
342
|
+
if (padSpan.width > 0 || padSpan.depth > 0) {
|
|
343
|
+
return {
|
|
344
|
+
source: 'pad-fallback',
|
|
345
|
+
reason: 'Projection fell back to nearby component pad span.',
|
|
346
|
+
boundsMil: {
|
|
347
|
+
width: padSpan.width,
|
|
348
|
+
depth: padSpan.depth,
|
|
349
|
+
height: Number(matchedComponent.height || 0)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const body = PcbScene3dPackages.resolve(matchedComponent, padSpan)
|
|
355
|
+
return {
|
|
356
|
+
source: 'component-fallback',
|
|
357
|
+
reason: 'Projection fell back to the procedural component body.',
|
|
358
|
+
boundsMil: {
|
|
359
|
+
width: body.sizeMil.width,
|
|
360
|
+
depth: body.sizeMil.depth,
|
|
361
|
+
height: body.sizeMil.height
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
source: 'model-anchor-fallback',
|
|
368
|
+
reason: 'Projection used the model anchor because no owner geometry was available.',
|
|
369
|
+
boundsMil: { width: 0, depth: 0, height: 0 }
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Returns the first complete bounds object from candidate metadata.
|
|
375
|
+
* @param {unknown[]} candidates Candidate bounds records.
|
|
376
|
+
* @returns {{ width: number, depth: number, height: number } | null}
|
|
377
|
+
*/
|
|
378
|
+
static #firstBounds(candidates) {
|
|
379
|
+
for (const candidate of candidates || []) {
|
|
380
|
+
const bounds = PcbScene3dBuilder.#normalizeBounds(candidate)
|
|
381
|
+
if (bounds) {
|
|
382
|
+
return bounds
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return null
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Normalizes width/depth/height bounds metadata.
|
|
391
|
+
* @param {unknown} candidate Candidate bounds record.
|
|
392
|
+
* @returns {{ width: number, depth: number, height: number } | null}
|
|
393
|
+
*/
|
|
394
|
+
static #normalizeBounds(candidate) {
|
|
395
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const width = Number(candidate.width ?? candidate.x ?? candidate.sizeX)
|
|
400
|
+
const depth = Number(candidate.depth ?? candidate.y ?? candidate.sizeY)
|
|
401
|
+
const height = Number(
|
|
402
|
+
candidate.height ?? candidate.z ?? candidate.sizeZ
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if (
|
|
406
|
+
!Number.isFinite(width) ||
|
|
407
|
+
!Number.isFinite(depth) ||
|
|
408
|
+
!Number.isFinite(height)
|
|
409
|
+
) {
|
|
410
|
+
return null
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return { width, depth, height }
|
|
414
|
+
}
|
|
415
|
+
|
|
282
416
|
/**
|
|
283
417
|
* Resolves explicit body placements to component anchors using a unique
|
|
284
418
|
* nearest-neighbor pass plus an ordered-affinity fallback for repeated
|
|
@@ -208,6 +208,49 @@ export class PcbScene3dModelRegistry {
|
|
|
208
208
|
return this.#resolveExplicitMatch(componentBody?.name)
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Resolves a project-level full board assembly model for one PCB document.
|
|
213
|
+
* @param {{ fileName?: string }} documentModel
|
|
214
|
+
* @returns {{ origin: 'board-assembly', file?: File | Blob | null, name: string, relativePath: string, format: string } | null}
|
|
215
|
+
*/
|
|
216
|
+
resolveBoardAssemblyModel(documentModel) {
|
|
217
|
+
const normalizedBoardBaseName = PcbScene3dModelRegistry.#normalizeToken(
|
|
218
|
+
PcbScene3dModelRegistry.#basenameWithoutExtension(
|
|
219
|
+
documentModel?.fileName
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
if (!normalizedBoardBaseName) {
|
|
223
|
+
return null
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const rankedMatches = this.#modelFiles
|
|
227
|
+
.filter(
|
|
228
|
+
(file) =>
|
|
229
|
+
file.normalizedBaseName === normalizedBoardBaseName &&
|
|
230
|
+
PcbScene3dModelRegistry.#isBoardAssemblyPath(
|
|
231
|
+
file.relativePath
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
.sort(
|
|
235
|
+
(left, right) =>
|
|
236
|
+
PcbScene3dModelRegistry.#formatRank(left.format) -
|
|
237
|
+
PcbScene3dModelRegistry.#formatRank(right.format)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if (!rankedMatches.length) {
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const matchedFile = rankedMatches[0]
|
|
245
|
+
return {
|
|
246
|
+
origin: 'board-assembly',
|
|
247
|
+
file: matchedFile.file,
|
|
248
|
+
name: matchedFile.name,
|
|
249
|
+
relativePath: matchedFile.relativePath,
|
|
250
|
+
format: matchedFile.format
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
211
254
|
/**
|
|
212
255
|
* Normalizes one embedded payload for registry lookup.
|
|
213
256
|
* @param {{ id?: string, checksum?: number | null, name?: string, format?: string, payloadText?: string, sourceStream?: string }} model
|
|
@@ -295,6 +338,37 @@ export class PcbScene3dModelRegistry {
|
|
|
295
338
|
return format === 'wrl' ? 0 : 1
|
|
296
339
|
}
|
|
297
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Checks whether one model path is in a conventional board model folder.
|
|
343
|
+
* @param {string | undefined} relativePath
|
|
344
|
+
* @returns {boolean}
|
|
345
|
+
*/
|
|
346
|
+
static #isBoardAssemblyPath(relativePath) {
|
|
347
|
+
return String(relativePath || '')
|
|
348
|
+
.replaceAll('\\', '/')
|
|
349
|
+
.split('/')
|
|
350
|
+
.some(
|
|
351
|
+
(part) =>
|
|
352
|
+
PcbScene3dModelRegistry.#normalizeToken(part) === '3dbodies'
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Returns one path basename without its extension.
|
|
358
|
+
* @param {string | undefined} filePath
|
|
359
|
+
* @returns {string}
|
|
360
|
+
*/
|
|
361
|
+
static #basenameWithoutExtension(filePath) {
|
|
362
|
+
const baseName =
|
|
363
|
+
String(filePath || '')
|
|
364
|
+
.replaceAll('\\', '/')
|
|
365
|
+
.split('/')
|
|
366
|
+
.filter(Boolean)
|
|
367
|
+
.at(-1) || ''
|
|
368
|
+
|
|
369
|
+
return baseName.replace(/\.[^.]+$/u, '')
|
|
370
|
+
}
|
|
371
|
+
|
|
298
372
|
/**
|
|
299
373
|
* Normalizes one lookup token.
|
|
300
374
|
* @param {string | undefined} value
|