altium-toolkit 1.1.35 → 1.1.36

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.
@@ -10,6 +10,8 @@ export class AltiumScene3dShapeStackOwnerAdapter {
10
10
  static #EXACT_OWNER_TOLERANCE_MIL = 1
11
11
  static #STACK_BODY_RADIUS_MIL = 220
12
12
  static #HEIGHT_TOLERANCE_MIL = 0.1
13
+ static #TIMING_PARAMETER_NAME_PATTERN =
14
+ /(?:^|[^a-z0-9])(?:comment|description|device|function|part|type|value)(?:$|[^a-z0-9])/i
13
15
 
14
16
  /**
15
17
  * Applies authored stack ownership to static and external placements.
@@ -169,21 +171,103 @@ export class AltiumScene3dShapeStackOwnerAdapter {
169
171
  return placement
170
172
  }
171
173
 
172
- return {
173
- ...placement,
174
- designator: String(assignment.owner.designator || ''),
175
- positionMil:
176
- AltiumScene3dShapeStackOwnerAdapter.#withOwnerAnchor(
177
- placement?.positionMil,
178
- assignment.owner,
179
- board
180
- ),
181
- coLocatedVariantGroupKey: assignment.groupKey
182
- }
174
+ return AltiumScene3dShapeStackOwnerAdapter.#withStaticOwner(
175
+ placement,
176
+ assignment,
177
+ board
178
+ )
183
179
  }
184
180
  )
185
181
  }
186
182
 
183
+ /**
184
+ * Applies one stack owner to a repaired static carrier body.
185
+ * @param {object} placement Static body placement.
186
+ * @param {{ owner: object, groupKey: string }} assignment Stack assignment.
187
+ * @param {object | undefined} board Scene board metadata.
188
+ * @returns {object}
189
+ */
190
+ static #withStaticOwner(placement, assignment, board) {
191
+ const ownerDesignator = String(assignment?.owner?.designator || '')
192
+ const mountSide =
193
+ AltiumScene3dShapeStackOwnerAdapter.#mountSide(assignment?.owner) ||
194
+ placement?.mountSide
195
+ const sidePlacement =
196
+ AltiumScene3dShapeStackOwnerAdapter.#withStaticMountSide(
197
+ placement,
198
+ mountSide
199
+ )
200
+
201
+ return {
202
+ ...sidePlacement,
203
+ designator: ownerDesignator,
204
+ selectionKey: ownerDesignator || sidePlacement.selectionKey,
205
+ positionMil: AltiumScene3dShapeStackOwnerAdapter.#withOwnerAnchor(
206
+ sidePlacement?.positionMil,
207
+ assignment.owner,
208
+ board
209
+ ),
210
+ coLocatedVariantGroupKey: assignment.groupKey
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Applies a static placement side while preserving its height magnitude.
216
+ * @param {object} placement Static body placement.
217
+ * @param {string | undefined} mountSide Owner mount side.
218
+ * @returns {object}
219
+ */
220
+ static #withStaticMountSide(placement, mountSide) {
221
+ const currentSide =
222
+ AltiumScene3dShapeStackOwnerAdapter.#normalizeMountSide(
223
+ placement?.mountSide
224
+ )
225
+ const side = AltiumScene3dShapeStackOwnerAdapter.#normalizeMountSide(
226
+ mountSide || currentSide
227
+ )
228
+ const z = Math.abs(Number(placement?.positionMil?.z || 0))
229
+
230
+ return {
231
+ ...placement,
232
+ mountSide: side,
233
+ positionMil: {
234
+ ...(placement?.positionMil || {}),
235
+ z: side === 'bottom' ? -z : z
236
+ },
237
+ geometry:
238
+ currentSide === side
239
+ ? placement?.geometry
240
+ : AltiumScene3dShapeStackOwnerAdapter.#mirrorStaticGeometry(
241
+ placement
242
+ )
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Mirrors source-coordinate static geometry for the opposite board side.
248
+ * @param {{ sourceCoordinateFrame?: boolean, geometry?: object }} placement Static body placement.
249
+ * @returns {object | undefined}
250
+ */
251
+ static #mirrorStaticGeometry(placement) {
252
+ const geometry = placement?.geometry
253
+ if (
254
+ !placement?.sourceCoordinateFrame ||
255
+ !Array.isArray(geometry?.verticesMil)
256
+ ) {
257
+ return geometry
258
+ }
259
+
260
+ return {
261
+ ...geometry,
262
+ verticesMil: geometry.verticesMil.map((vertex) => ({
263
+ x: AltiumScene3dShapeStackOwnerAdapter.#round(vertex?.x || 0),
264
+ y: AltiumScene3dShapeStackOwnerAdapter.#round(
265
+ -Number(vertex?.y || 0)
266
+ )
267
+ }))
268
+ }
269
+ }
270
+
187
271
  /**
188
272
  * Re-anchors a repaired static carrier on the owning component position.
189
273
  * @param {object | undefined} position Existing render position.
@@ -630,15 +714,9 @@ export class AltiumScene3dShapeStackOwnerAdapter {
630
714
  return (
631
715
  TIMING_DESIGNATOR_PATTERN.test(designator) ||
632
716
  TIMING_PACKAGE_PATTERN.test(
633
- [
634
- component?.pattern,
635
- component?.source,
636
- component?.description,
637
- component?.provenance?.footprintDescription,
638
- ...Object.values(component?.parameters || {})
639
- ]
640
- .map((value) => String(value || ''))
641
- .join(' ')
717
+ AltiumScene3dShapeStackOwnerAdapter.#timingIdentityText(
718
+ component
719
+ )
642
720
  )
643
721
  )
644
722
  }
@@ -691,6 +769,19 @@ export class AltiumScene3dShapeStackOwnerAdapter {
691
769
  return layer.includes('BOTTOM') || layer === 'BOT' ? 'bottom' : 'top'
692
770
  }
693
771
 
772
+ /**
773
+ * Normalizes a static placement side token.
774
+ * @param {string | undefined} mountSide Candidate mount side.
775
+ * @returns {'top' | 'bottom'}
776
+ */
777
+ static #normalizeMountSide(mountSide) {
778
+ return String(mountSide || 'top')
779
+ .trim()
780
+ .toLowerCase() === 'bottom'
781
+ ? 'bottom'
782
+ : 'top'
783
+ }
784
+
694
785
  /**
695
786
  * Resolves one board face Z coordinate.
696
787
  * @param {string} mountSide Mount side.
@@ -770,6 +861,44 @@ export class AltiumScene3dShapeStackOwnerAdapter {
770
861
  .join(' ')
771
862
  }
772
863
 
864
+ /**
865
+ * Builds timing-relevant identity text without material metadata.
866
+ * @param {object} component Source component.
867
+ * @returns {string}
868
+ */
869
+ static #timingIdentityText(component) {
870
+ return [
871
+ component?.pattern,
872
+ component?.source,
873
+ component?.description,
874
+ component?.provenance?.footprintDescription,
875
+ ...AltiumScene3dShapeStackOwnerAdapter.#timingParameterValues(
876
+ component?.parameters
877
+ )
878
+ ]
879
+ .map((value) => String(value || ''))
880
+ .join(' ')
881
+ }
882
+
883
+ /**
884
+ * Returns parameter values whose names describe component identity.
885
+ * @param {object | undefined} parameters Source component parameters.
886
+ * @returns {unknown[]}
887
+ */
888
+ static #timingParameterValues(parameters) {
889
+ if (!parameters || typeof parameters !== 'object') {
890
+ return []
891
+ }
892
+
893
+ return Object.entries(parameters)
894
+ .filter(([key]) =>
895
+ AltiumScene3dShapeStackOwnerAdapter.#TIMING_PARAMETER_NAME_PATTERN.test(
896
+ String(key || '')
897
+ )
898
+ )
899
+ .map(([, value]) => value)
900
+ }
901
+
773
902
  /**
774
903
  * Rounds one mil value for stable scene output.
775
904
  * @param {number} value Candidate value.
@@ -54,7 +54,7 @@ export class PcbScene3dPackages {
54
54
  static #resolveFamily(pattern) {
55
55
  const normalized = String(pattern || '').toUpperCase()
56
56
 
57
- if (/(0402|0603|0805|1206|C0805|C0603)/.test(normalized)) {
57
+ if (PcbScene3dPackages.#hasChipPackageCode(normalized)) {
58
58
  return 'chip'
59
59
  }
60
60
 
@@ -97,6 +97,15 @@ export class PcbScene3dPackages {
97
97
  return 'generic'
98
98
  }
99
99
 
100
+ /**
101
+ * Checks whether a footprint pattern contains a compact chip package code.
102
+ * @param {string} normalized Uppercase footprint pattern.
103
+ * @returns {boolean}
104
+ */
105
+ static #hasChipPackageCode(normalized) {
106
+ return /(01005|0201|0402|0603|0805|1206|C0805|C0603)/.test(normalized)
107
+ }
108
+
100
109
  /**
101
110
  * Resolves one default body size for the chosen family.
102
111
  * @param {string} family
@@ -107,6 +116,12 @@ export class PcbScene3dPackages {
107
116
  const normalized = String(pattern || '').toUpperCase()
108
117
 
109
118
  if (family === 'chip') {
119
+ if (normalized.includes('01005')) {
120
+ return { width: 16, depth: 8, height: 8 }
121
+ }
122
+ if (normalized.includes('0201')) {
123
+ return { width: 24, depth: 12, height: 14 }
124
+ }
110
125
  if (normalized.includes('0402')) {
111
126
  return { width: 24, depth: 12, height: 14 }
112
127
  }