altium-toolkit 1.1.30 → 1.1.32

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.
@@ -8,12 +8,14 @@ import { PcbScene3dDrillCutoutBuilder } from './PcbScene3dDrillCutoutBuilder.mjs
8
8
  import { AltiumScene3dExternalPlacementAdapter } from './AltiumScene3dExternalPlacementAdapter.mjs'
9
9
  import { AltiumScene3dBottomPadRotationAdapter } from './AltiumScene3dBottomPadRotationAdapter.mjs'
10
10
  import { AltiumScene3dComponentBodyAdapter } from './AltiumScene3dComponentBodyAdapter.mjs'
11
+ import { AltiumScene3dAuthoredBodyAnchorAdapter } from './AltiumScene3dAuthoredBodyAnchorAdapter.mjs'
11
12
  import { PcbFootprintPrimitiveSelector } from './PcbFootprintPrimitiveSelector.mjs'
12
13
  import { PcbScene3dPadLocalSpanResolver } from './PcbScene3dPadLocalSpanResolver.mjs'
13
14
  import { PcbScene3dPackages } from './PcbScene3dPackages.mjs'
14
15
  import { PcbScene3dPlacementSideResolver } from './PcbScene3dPlacementSideResolver.mjs'
15
16
  import { PcbScene3dStaticBodyPlacementBuilder } from './PcbScene3dStaticBodyPlacementBuilder.mjs'
16
17
  import { PcbScene3dTextBoxLayoutResolver } from './PcbScene3dTextBoxLayoutResolver.mjs'
18
+ import { PcbFootprintPadAxisNormalizer } from './PcbFootprintPadAxisNormalizer.mjs'
17
19
 
18
20
  /**
19
21
  * Builds deterministic 3D scene data from the normalized PCB model.
@@ -24,10 +26,16 @@ export class PcbScene3dBuilder {
24
26
  static #DENSE_OVERLAY_MIN_TRACK_COUNT = 250
25
27
  static #DENSE_OVERLAY_KNOCKOUT_COLOR = 0x2f6a2c
26
28
  static #PRECISE_BODY_MATCH_TOLERANCE_MIL = 20
29
+ static #EXACT_BODY_MISMATCH_TOLERANCE_MIL = 1
30
+ static #NEAR_PACKAGE_AFFINITY_DISTANCE_MIL = 100
27
31
  static #UNMATCHED_BODY_OVERHANG_RATIO = 0.25
28
32
  static #UNMATCHED_BODY_MIN_OVERHANG_MIL = 150
29
33
  static #UNMATCHED_BODY_MAX_OVERHANG_MIL = 600
30
34
  static #TRUETYPE_TEXT_WIDTH_RATIO = 0.55
35
+ static #AUTHORED_BODY_IDENTITY_PATTERN =
36
+ /(?:^|[^a-z0-9])(?:antenna|coax|conn|connector|edge|flex|fpc|frame|hardware|header|jack|mechanical|module|mount|shield|sma|socket|usb)(?:$|[^a-z0-9])/i
37
+ static #COMPONENT_PACKAGE_BODY_PATTERN =
38
+ /(?:^|[^a-z0-9])(?:[a-z0-9]*dfn|[a-z0-9]*qfn|bga|cap|capacitor|crystal|diode|ferrite|ind|inductor|lga|lqg[a-z0-9]*|lqw[a-z0-9]*|osc|qfp|res|resistor|sot|transistor|xtal)(?:$|[^a-z0-9])/i
31
39
 
32
40
  /**
33
41
  * Builds a scene description for host 3D renderers.
@@ -36,7 +44,9 @@ export class PcbScene3dBuilder {
36
44
  * @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 } }[], staticBodyPlacements: { designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, bodyPositionMil: { x: number, y: number }, geometry: object }[], 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 } } } }}
37
45
  */
38
46
  static build(documentModel, options = {}) {
39
- const pcb = documentModel?.pcb || {}
47
+ const sceneDocumentModel =
48
+ PcbFootprintPadAxisNormalizer.apply(documentModel)
49
+ const pcb = sceneDocumentModel?.pcb || {}
40
50
  const appearance3d = pcb.appearance3d || {}
41
51
  const boardOutline = pcb.boardOutline || {}
42
52
  const primitiveLayers = Array.isArray(pcb.primitiveLayers)
@@ -77,10 +87,16 @@ export class PcbScene3dBuilder {
77
87
  : []
78
88
  }
79
89
  const componentBodyModels = componentBodies.map((componentBody) =>
80
- PcbScene3dBuilder.#resolveComponentBodyModel(
90
+ PcbScene3dBuilder.#shouldSuppressLayerlessBodyPlaceholder(
81
91
  componentBody,
82
- modelRegistry
92
+ componentBodies,
93
+ board
83
94
  )
95
+ ? null
96
+ : PcbScene3dBuilder.#resolveComponentBodyModel(
97
+ componentBody,
98
+ modelRegistry
99
+ )
84
100
  )
85
101
  const bodyMatches = PcbScene3dBuilder.#resolveComponentBodyMatches(
86
102
  componentBodies,
@@ -171,28 +187,30 @@ export class PcbScene3dBuilder {
171
187
  }
172
188
  }
173
189
 
174
- return AltiumScene3dBottomPadRotationAdapter.apply(
175
- AltiumScene3dComponentBodyAdapter.apply(
176
- AltiumScene3dExternalPlacementAdapter.apply(
177
- PcbScene3dBoardOutlineRefiner.refine(
178
- sceneDescription,
179
- documentModel
190
+ return AltiumScene3dAuthoredBodyAnchorAdapter.apply(
191
+ AltiumScene3dBottomPadRotationAdapter.apply(
192
+ AltiumScene3dComponentBodyAdapter.apply(
193
+ AltiumScene3dExternalPlacementAdapter.apply(
194
+ PcbScene3dBoardOutlineRefiner.refine(
195
+ sceneDescription,
196
+ sceneDocumentModel
197
+ ),
198
+ sceneDocumentModel
180
199
  ),
181
- documentModel
182
- ),
183
- documentModel
200
+ sceneDocumentModel
201
+ )
184
202
  )
185
203
  )
186
204
  }
187
205
 
188
206
  /**
189
207
  * Builds one procedural component scene entry.
190
- * @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, rotation?: number, height?: number | null, source?: string, modelPath?: string }} component
208
+ * @param {{ designator: string, x: number, y: number, layer?: string, pattern?: string, rotation?: number, height?: number | null, source?: string, description?: string, parameters?: Record<string, unknown>, modelPath?: string }} component
191
209
  * @param {{ x: number, y: number, sizeTopX?: number, sizeTopY?: number, sizeMidX?: number, sizeMidY?: number, sizeBottomX?: number, sizeBottomY?: number }[]} pads
192
210
  * @param {{ centerX: number, centerY: number }} board
193
211
  * @param {number} thicknessMil
194
212
  * @param {{ resolveComponentModel: (component: any) => { name: string, relativePath: string, format: string } | null } | null} modelRegistry
195
- * @returns {{ 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 }}
213
+ * @returns {{ designator: string, mountSide: string, rotationDeg: number, positionMil: { x: number, y: number, z: number }, boardPositionMil: { x: number, y: number, z: number }, pattern: string, source: string, description: string, parameters: Record<string, unknown>, body: { family: string, sizeMil: { width: number, depth: number, height: number } }, externalModel: { name: string, relativePath: string, format: string } | null }}
196
214
  */
197
215
  static #buildComponent(
198
216
  component,
@@ -227,6 +245,13 @@ export class PcbScene3dBuilder {
227
245
  },
228
246
  pattern: String(component.pattern || ''),
229
247
  source: String(component.source || ''),
248
+ description: String(component.description || ''),
249
+ parameters:
250
+ component.parameters &&
251
+ typeof component.parameters === 'object' &&
252
+ !Array.isArray(component.parameters)
253
+ ? { ...component.parameters }
254
+ : {},
230
255
  body,
231
256
  externalModel: modelRegistry
232
257
  ? modelRegistry.resolveComponentModel(component)
@@ -269,6 +294,16 @@ export class PcbScene3dBuilder {
269
294
  return null
270
295
  }
271
296
 
297
+ if (
298
+ !matchedComponent &&
299
+ PcbScene3dBuilder.#shouldDropUnmatchedPackageBody(
300
+ componentBody,
301
+ components
302
+ )
303
+ ) {
304
+ return null
305
+ }
306
+
272
307
  if (
273
308
  !matchedComponent &&
274
309
  !PcbScene3dBuilder.#isBodyPositionNearBoard(componentBody, board)
@@ -534,6 +569,12 @@ export class PcbScene3dBuilder {
534
569
  bodyIndex,
535
570
  componentIndex,
536
571
  affinityScore,
572
+ nearPackageAffinityScore:
573
+ PcbScene3dBuilder.#nearPackageAffinityScore(
574
+ componentBody,
575
+ affinityScore,
576
+ distance
577
+ ),
537
578
  preciseOwnerScore:
538
579
  precise && (sideCompatible || affinityScore > 0)
539
580
  ? 1
@@ -549,6 +590,8 @@ export class PcbScene3dBuilder {
549
590
  closeCandidates
550
591
  .sort(
551
592
  (left, right) =>
593
+ right.nearPackageAffinityScore -
594
+ left.nearPackageAffinityScore ||
552
595
  right.preciseOwnerScore - left.preciseOwnerScore ||
553
596
  right.sideAffinityScore - left.sideAffinityScore ||
554
597
  right.affinityScore - left.affinityScore ||
@@ -798,7 +841,11 @@ export class PcbScene3dBuilder {
798
841
  distanceMil
799
842
  ) {
800
843
  if (PcbScene3dBuilder.#isPreciseBodyComponentDistance(distanceMil)) {
801
- return true
844
+ return !PcbScene3dBuilder.#isIncompatiblePackageBodyMatch(
845
+ componentBody,
846
+ component,
847
+ distanceMil
848
+ )
802
849
  }
803
850
 
804
851
  if (
@@ -819,6 +866,242 @@ export class PcbScene3dBuilder {
819
866
  return bodyCount > 0 && bodyCount <= candidateCount
820
867
  }
821
868
 
869
+ /**
870
+ * Scores short-range package metadata matches above no-affinity anchors.
871
+ * @param {{ name?: string, identifier?: string }} componentBody Component-body record.
872
+ * @param {number} affinityScore Shared body/component identity score.
873
+ * @param {number} distanceMil Body/component anchor distance.
874
+ * @returns {number}
875
+ */
876
+ static #nearPackageAffinityScore(
877
+ componentBody,
878
+ affinityScore,
879
+ distanceMil
880
+ ) {
881
+ return PcbScene3dBuilder.#isComponentPackageBody(componentBody) &&
882
+ Number(affinityScore || 0) > 0 &&
883
+ Number(distanceMil || 0) <=
884
+ PcbScene3dBuilder.#NEAR_PACKAGE_AFFINITY_DISTANCE_MIL
885
+ ? 1
886
+ : 0
887
+ }
888
+
889
+ /**
890
+ * Checks whether an unmatched package-like body should be suppressed
891
+ * because it sits on an exact but incompatible component anchor.
892
+ * @param {{ name?: string, identifier?: string, positionMil?: { x?: number, y?: number } }} componentBody Component-body record.
893
+ * @param {{ x: number, y: number, pattern?: string, source?: string, modelPath?: string }[]} components PCB components.
894
+ * @returns {boolean}
895
+ */
896
+ static #shouldDropUnmatchedPackageBody(componentBody, components) {
897
+ if (
898
+ !PcbScene3dBuilder.#isComponentPackageBody(componentBody) ||
899
+ PcbScene3dBuilder.#isAuthoredBodyIdentity(componentBody)
900
+ ) {
901
+ return false
902
+ }
903
+
904
+ const exactComponents = (
905
+ Array.isArray(components) ? components : []
906
+ ).filter(
907
+ (component) =>
908
+ PcbScene3dBuilder.#distanceBetweenBodyAndComponent(
909
+ componentBody,
910
+ component
911
+ ) <= PcbScene3dBuilder.#EXACT_BODY_MISMATCH_TOLERANCE_MIL
912
+ )
913
+
914
+ return (
915
+ exactComponents.length > 0 &&
916
+ exactComponents.every(
917
+ (component) =>
918
+ PcbScene3dPlacementSideResolver.scoreBodyComponentAffinity(
919
+ componentBody,
920
+ component
921
+ ) <= 0
922
+ )
923
+ )
924
+ }
925
+
926
+ /**
927
+ * Checks whether a precise body/component pair is a package-family mismatch.
928
+ * @param {{ name?: string, identifier?: string }} componentBody Component-body record.
929
+ * @param {{ pattern?: string, source?: string, modelPath?: string }} component PCB component.
930
+ * @param {number} distanceMil Body/component anchor distance.
931
+ * @returns {boolean}
932
+ */
933
+ static #isIncompatiblePackageBodyMatch(
934
+ componentBody,
935
+ component,
936
+ distanceMil
937
+ ) {
938
+ return (
939
+ Number(distanceMil || 0) <=
940
+ PcbScene3dBuilder.#EXACT_BODY_MISMATCH_TOLERANCE_MIL &&
941
+ PcbScene3dBuilder.#isComponentPackageBody(componentBody) &&
942
+ !PcbScene3dBuilder.#isAuthoredBodyIdentity(componentBody) &&
943
+ PcbScene3dPlacementSideResolver.scoreBodyComponentAffinity(
944
+ componentBody,
945
+ component
946
+ ) <= 0
947
+ )
948
+ }
949
+
950
+ /**
951
+ * Checks whether a component body names a package model rather than an
952
+ * authored board mechanical.
953
+ * @param {{ name?: string, identifier?: string }} componentBody Component-body record.
954
+ * @returns {boolean}
955
+ */
956
+ static #isComponentPackageBody(componentBody) {
957
+ return PcbScene3dBuilder.#COMPONENT_PACKAGE_BODY_PATTERN.test(
958
+ PcbScene3dBuilder.#componentBodyIdentityText(componentBody)
959
+ )
960
+ }
961
+
962
+ /**
963
+ * Checks whether a component body identity describes an authored hardware
964
+ * or connector anchor that can legitimately sit away from a footprint
965
+ * center.
966
+ * @param {{ name?: string, identifier?: string }} componentBody Component-body record.
967
+ * @returns {boolean}
968
+ */
969
+ static #isAuthoredBodyIdentity(componentBody) {
970
+ return PcbScene3dBuilder.#AUTHORED_BODY_IDENTITY_PATTERN.test(
971
+ PcbScene3dBuilder.#componentBodyIdentityText(componentBody)
972
+ )
973
+ }
974
+
975
+ /**
976
+ * Checks whether a layerless component body is a footprint-library
977
+ * placeholder rather than a board-side placement.
978
+ * @param {{ layer?: string, name?: string, identifier?: string, modelId?: string, checksum?: number | null, positionMil?: { x?: number, y?: number } }} componentBody Component body record.
979
+ * @param {object[]} componentBodies All component body records.
980
+ * @param {{ minX: number, minY: number, widthMil: number, heightMil: number }} board Board envelope.
981
+ * @returns {boolean}
982
+ */
983
+ static #shouldSuppressLayerlessBodyPlaceholder(
984
+ componentBody,
985
+ componentBodies,
986
+ board
987
+ ) {
988
+ if (
989
+ !PcbScene3dBuilder.#isLayerlessComponentBody(componentBody) ||
990
+ PcbScene3dBuilder.#isAuthoredBodyIdentity(componentBody)
991
+ ) {
992
+ return false
993
+ }
994
+
995
+ return (
996
+ !PcbScene3dBuilder.#isBodyPositionNearBoard(componentBody, board) ||
997
+ PcbScene3dBuilder.#hasLayeredEquivalentPackageBody(
998
+ componentBody,
999
+ componentBodies
1000
+ )
1001
+ )
1002
+ }
1003
+
1004
+ /**
1005
+ * Checks whether a body lacks an authored mechanical layer.
1006
+ * @param {{ layer?: string }} componentBody Component body record.
1007
+ * @returns {boolean}
1008
+ */
1009
+ static #isLayerlessComponentBody(componentBody) {
1010
+ return String(componentBody?.layer || '').trim() === ''
1011
+ }
1012
+
1013
+ /**
1014
+ * Checks whether a layerless body duplicates a real board-side body.
1015
+ * @param {{ positionMil?: { x?: number, y?: number } }} componentBody Candidate layerless body.
1016
+ * @param {object[]} componentBodies All component body records.
1017
+ * @returns {boolean}
1018
+ */
1019
+ static #hasLayeredEquivalentPackageBody(componentBody, componentBodies) {
1020
+ return (Array.isArray(componentBodies) ? componentBodies : []).some(
1021
+ (candidate) =>
1022
+ candidate !== componentBody &&
1023
+ !PcbScene3dBuilder.#isLayerlessComponentBody(candidate) &&
1024
+ PcbScene3dBuilder.#componentBodiesShareModelIdentity(
1025
+ componentBody,
1026
+ candidate
1027
+ ) &&
1028
+ PcbScene3dBuilder.#distanceBetweenBodies(
1029
+ componentBody,
1030
+ candidate
1031
+ ) <= PcbScene3dBuilder.#EXACT_BODY_MISMATCH_TOLERANCE_MIL
1032
+ )
1033
+ }
1034
+
1035
+ /**
1036
+ * Checks whether two component bodies refer to the same external package.
1037
+ * @param {{ name?: string, identifier?: string, modelId?: string, checksum?: number | null }} left First body.
1038
+ * @param {{ name?: string, identifier?: string, modelId?: string, checksum?: number | null }} right Second body.
1039
+ * @returns {boolean}
1040
+ */
1041
+ static #componentBodiesShareModelIdentity(left, right) {
1042
+ const leftModelId = PcbScene3dBuilder.#normalizeIdentityToken(
1043
+ left?.modelId
1044
+ )
1045
+ const rightModelId = PcbScene3dBuilder.#normalizeIdentityToken(
1046
+ right?.modelId
1047
+ )
1048
+ if (leftModelId && rightModelId && leftModelId === rightModelId) {
1049
+ return true
1050
+ }
1051
+
1052
+ const leftChecksum = Number(left?.checksum)
1053
+ const rightChecksum = Number(right?.checksum)
1054
+ if (
1055
+ Number.isFinite(leftChecksum) &&
1056
+ Number.isFinite(rightChecksum) &&
1057
+ leftChecksum === rightChecksum
1058
+ ) {
1059
+ return true
1060
+ }
1061
+
1062
+ const leftName = PcbScene3dBuilder.#normalizeBodyNameToken(left)
1063
+ const rightName = PcbScene3dBuilder.#normalizeBodyNameToken(right)
1064
+
1065
+ return Boolean(leftName && rightName && leftName === rightName)
1066
+ }
1067
+
1068
+ /**
1069
+ * Normalizes a body model name or identifier for duplicate detection.
1070
+ * @param {{ name?: string, identifier?: string }} componentBody Component body.
1071
+ * @returns {string}
1072
+ */
1073
+ static #normalizeBodyNameToken(componentBody) {
1074
+ return PcbScene3dBuilder.#normalizeIdentityToken(
1075
+ componentBody?.name || componentBody?.identifier
1076
+ ).replace(/\.[a-z0-9]+$/i, '')
1077
+ }
1078
+
1079
+ /**
1080
+ * Normalizes an identity token for case-insensitive comparisons.
1081
+ * @param {unknown} value Raw token.
1082
+ * @returns {string}
1083
+ */
1084
+ static #normalizeIdentityToken(value) {
1085
+ return String(value || '')
1086
+ .trim()
1087
+ .toLowerCase()
1088
+ }
1089
+
1090
+ /**
1091
+ * Builds searchable identity text for one component body.
1092
+ * @param {{ name?: string, identifier?: string, modelId?: string }} componentBody Component-body record.
1093
+ * @returns {string}
1094
+ */
1095
+ static #componentBodyIdentityText(componentBody) {
1096
+ return [
1097
+ componentBody?.identifier,
1098
+ componentBody?.name,
1099
+ componentBody?.modelId
1100
+ ]
1101
+ .map((value) => String(value || ''))
1102
+ .join(' ')
1103
+ }
1104
+
822
1105
  /**
823
1106
  * Pairs one unresolved repeated body group with a repeated component group
824
1107
  * by preserving the dominant ordering axis and choosing the pairing that
@@ -1535,6 +1818,21 @@ export class PcbScene3dBuilder {
1535
1818
  )
1536
1819
  }
1537
1820
 
1821
+ /**
1822
+ * Returns the euclidean distance between two component body anchors.
1823
+ * @param {{ positionMil?: { x?: number, y?: number } }} left First body.
1824
+ * @param {{ positionMil?: { x?: number, y?: number } }} right Second body.
1825
+ * @returns {number}
1826
+ */
1827
+ static #distanceBetweenBodies(left, right) {
1828
+ return Math.hypot(
1829
+ Number(left?.positionMil?.x || 0) -
1830
+ Number(right?.positionMil?.x || 0),
1831
+ Number(left?.positionMil?.y || 0) -
1832
+ Number(right?.positionMil?.y || 0)
1833
+ )
1834
+ }
1835
+
1538
1836
  /**
1539
1837
  * Returns true when one unresolved body anchor still lies close enough to
1540
1838
  * the board envelope to be renderable without a component match.
@@ -81,7 +81,7 @@ export class PcbScene3dPlacementSideResolver {
81
81
  * Scores how strongly one component record appears to belong to one body
82
82
  * record based on shared model/footprint tokens.
83
83
  * @param {{ name?: string, identifier?: string }} componentBody
84
- * @param {{ pattern?: string, source?: string, modelPath?: string }} component
84
+ * @param {{ pattern?: string, source?: string, modelPath?: string, description?: string, parameters?: object, provenance?: object }} component
85
85
  * @returns {number}
86
86
  */
87
87
  static scoreBodyComponentAffinity(componentBody, component) {
@@ -94,7 +94,11 @@ export class PcbScene3dPlacementSideResolver {
94
94
  PcbScene3dPlacementSideResolver.#collectMeaningfulTokens([
95
95
  component?.pattern,
96
96
  component?.source,
97
- component?.modelPath
97
+ component?.modelPath,
98
+ component?.description,
99
+ ...PcbScene3dPlacementSideResolver.#componentPackageMetadata(
100
+ component
101
+ )
98
102
  ])
99
103
  let score = 0
100
104
 
@@ -107,6 +111,34 @@ export class PcbScene3dPlacementSideResolver {
107
111
  return score
108
112
  }
109
113
 
114
+ /**
115
+ * Resolves package-related component metadata that can identify generic
116
+ * embedded 3D bodies when pattern/source only name the electrical part.
117
+ * @param {{ parameters?: object, provenance?: object } | null | undefined} component Component record.
118
+ * @returns {string[]}
119
+ */
120
+ static #componentPackageMetadata(component) {
121
+ const parameters =
122
+ component?.parameters && typeof component.parameters === 'object'
123
+ ? component.parameters
124
+ : {}
125
+ const provenance =
126
+ component?.provenance && typeof component.provenance === 'object'
127
+ ? component.provenance
128
+ : {}
129
+
130
+ return [
131
+ parameters['Package / Case'],
132
+ parameters['Supplier Device Package'],
133
+ parameters['Part Description'],
134
+ parameters.Package,
135
+ provenance.footprintDescription,
136
+ provenance.sourceLibReference,
137
+ provenance.sourceFootprintLibrary,
138
+ provenance.sourceFootprintLibraryName
139
+ ].map((value) => String(value || ''))
140
+ }
141
+
110
142
  /**
111
143
  * Resolves side from the normalized component layer.
112
144
  * @param {string | undefined} layer