altium-toolkit 1.1.25 → 1.1.30

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.
@@ -0,0 +1,153 @@
1
+ import { PcbScene3dPadLocalSpanResolver } from './PcbScene3dPadLocalSpanResolver.mjs'
2
+
3
+ const REFINABLE_FAMILIES = new Set(['chip', 'diode', 'generic', 'ic', 'sot'])
4
+
5
+ /**
6
+ * Refines Altium procedural fallback body sizes from component-owned pads.
7
+ */
8
+ export class AltiumScene3dComponentBodyAdapter {
9
+ static #OVERSIZE_RATIO = 1.75
10
+
11
+ /**
12
+ * Applies owned-pad body refinement to an Altium scene description.
13
+ * @param {object} sceneDescription Scene description.
14
+ * @param {object} documentModel Source document model.
15
+ * @returns {object}
16
+ */
17
+ static apply(sceneDescription, documentModel) {
18
+ if (
19
+ String(sceneDescription?.sourceFormat || '').toLowerCase() !==
20
+ 'altium' ||
21
+ !Array.isArray(sceneDescription?.components)
22
+ ) {
23
+ return sceneDescription
24
+ }
25
+
26
+ const sourceComponents = Array.isArray(documentModel?.pcb?.components)
27
+ ? documentModel.pcb.components
28
+ : []
29
+ const pads = Array.isArray(documentModel?.pcb?.pads)
30
+ ? documentModel.pcb.pads
31
+ : []
32
+ if (!sourceComponents.length || !pads.length) {
33
+ return sceneDescription
34
+ }
35
+
36
+ const sourceByDesignator = new Map(
37
+ sourceComponents.map((component) => [
38
+ String(component?.designator || ''),
39
+ component
40
+ ])
41
+ )
42
+
43
+ return {
44
+ ...sceneDescription,
45
+ components: sceneDescription.components.map((component) =>
46
+ AltiumScene3dComponentBodyAdapter.#refineComponent(
47
+ component,
48
+ sourceByDesignator.get(String(component?.designator || '')),
49
+ pads
50
+ )
51
+ )
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Refines one procedural component body when nearby pads overinflated it.
57
+ * @param {object} component Scene component.
58
+ * @param {object | undefined} sourceComponent Source PCB component.
59
+ * @param {object[]} pads Source PCB pads.
60
+ * @returns {object}
61
+ */
62
+ static #refineComponent(component, sourceComponent, pads) {
63
+ const family = String(component?.body?.family || '')
64
+ if (
65
+ component?.externalModel ||
66
+ !sourceComponent ||
67
+ !REFINABLE_FAMILIES.has(family)
68
+ ) {
69
+ return component
70
+ }
71
+
72
+ const span = AltiumScene3dComponentBodyAdapter.#ownedPadSpan(
73
+ sourceComponent,
74
+ component.mountSide,
75
+ pads
76
+ )
77
+ const size = component?.body?.sizeMil || {}
78
+ if (
79
+ !span ||
80
+ !AltiumScene3dComponentBodyAdapter.#isOversized(size, span)
81
+ ) {
82
+ return component
83
+ }
84
+
85
+ return {
86
+ ...component,
87
+ body: {
88
+ ...component.body,
89
+ sizeMil: {
90
+ ...size,
91
+ width: span.width,
92
+ depth: span.depth
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Resolves the owned surface-pad span for one source component.
100
+ * @param {object} component Source component.
101
+ * @param {string} mountSide Component mount side.
102
+ * @param {object[]} pads Source PCB pads.
103
+ * @returns {{ width: number, depth: number } | null}
104
+ */
105
+ static #ownedPadSpan(component, mountSide, pads) {
106
+ const componentIndex = Number(component?.componentIndex)
107
+ if (!Number.isFinite(componentIndex)) {
108
+ return null
109
+ }
110
+
111
+ const ownedPads = pads.filter(
112
+ (pad) => Number(pad?.componentIndex) === componentIndex
113
+ )
114
+ const surfacePads = ownedPads.filter((pad) =>
115
+ AltiumScene3dComponentBodyAdapter.#isSurfacePad(pad, mountSide)
116
+ )
117
+ const spanPads = surfacePads.length ? surfacePads : ownedPads
118
+
119
+ return PcbScene3dPadLocalSpanResolver.resolve(
120
+ component,
121
+ spanPads,
122
+ mountSide
123
+ )
124
+ }
125
+
126
+ /**
127
+ * Checks whether one pad belongs to the component's mounted surface.
128
+ * @param {object} pad Source pad.
129
+ * @param {string} mountSide Component mount side.
130
+ * @returns {boolean}
131
+ */
132
+ static #isSurfacePad(pad, mountSide) {
133
+ return String(mountSide || '').toLowerCase() === 'bottom'
134
+ ? Boolean(pad?.hasBottomPasteMaskOpening)
135
+ : Boolean(pad?.hasTopPasteMaskOpening)
136
+ }
137
+
138
+ /**
139
+ * Checks whether the current body is clearly larger than owned pads.
140
+ * @param {object} size Current body size.
141
+ * @param {{ width: number, depth: number }} span Owned pad span.
142
+ * @returns {boolean}
143
+ */
144
+ static #isOversized(size, span) {
145
+ return (
146
+ Number(size?.width || 0) >
147
+ span.width *
148
+ AltiumScene3dComponentBodyAdapter.#OVERSIZE_RATIO ||
149
+ Number(size?.depth || 0) >
150
+ span.depth * AltiumScene3dComponentBodyAdapter.#OVERSIZE_RATIO
151
+ )
152
+ }
153
+ }