altium-toolkit 1.1.0 → 1.1.2
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/package.json +1 -1
- package/src/core/altium/NaturalStringComparator.mjs +175 -0
- package/src/core/altium/ParserUtils.mjs +83 -14
- package/src/core/altium/PcbComponentPrimitiveIndexer.mjs +104 -33
- package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +26 -4
- package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +28 -5
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +1 -3
- package/src/core/altium/PcbReviewMetadataBuilder.mjs +59 -28
- package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +66 -29
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +50 -16
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +36 -11
- package/src/core/altium/SchematicImplementationParser.mjs +75 -36
- package/src/core/altium/SchematicQaReportBuilder.mjs +115 -38
- package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +155 -5
- package/src/ui/PcbInteractionIndex.mjs +16 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { PcbReviewDrillMetadataBuilder } from './PcbReviewDrillMetadataBuilder.mjs'
|
|
6
6
|
import { PcbReviewPolygonRealizationBuilder } from './PcbReviewPolygonRealizationBuilder.mjs'
|
|
7
7
|
import { PcbReviewRouteHighlightProfileBuilder } from './PcbReviewRouteHighlightProfileBuilder.mjs'
|
|
8
|
+
import { NaturalStringComparator } from './NaturalStringComparator.mjs'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Builds PCB review metadata for routed-class and board-assembly workflows.
|
|
@@ -18,11 +19,15 @@ export class PcbReviewMetadataBuilder {
|
|
|
18
19
|
* @returns {object}
|
|
19
20
|
*/
|
|
20
21
|
static build(pcb = {}) {
|
|
22
|
+
const routeAnalysis = pcb.routeAnalysis || {}
|
|
23
|
+
const routeRowsByName =
|
|
24
|
+
PcbReviewMetadataBuilder.#routeRowsByName(routeAnalysis)
|
|
21
25
|
const routeGroups = PcbReviewMetadataBuilder.#routeGroups(
|
|
22
|
-
|
|
26
|
+
routeAnalysis,
|
|
27
|
+
routeRowsByName
|
|
23
28
|
)
|
|
24
29
|
const routeHighlightProfiles =
|
|
25
|
-
PcbReviewRouteHighlightProfileBuilder.build(
|
|
30
|
+
PcbReviewRouteHighlightProfileBuilder.build(routeAnalysis)
|
|
26
31
|
const polygonRealizations =
|
|
27
32
|
PcbReviewPolygonRealizationBuilder.build(pcb)
|
|
28
33
|
const drillReview = PcbReviewDrillMetadataBuilder.build(pcb)
|
|
@@ -51,7 +56,7 @@ export class PcbReviewMetadataBuilder {
|
|
|
51
56
|
polygonRealizations,
|
|
52
57
|
drillReview,
|
|
53
58
|
boardAssemblyViews,
|
|
54
|
-
|
|
59
|
+
routeAnalysis
|
|
55
60
|
)
|
|
56
61
|
}
|
|
57
62
|
}
|
|
@@ -59,21 +64,29 @@ export class PcbReviewMetadataBuilder {
|
|
|
59
64
|
/**
|
|
60
65
|
* Builds route highlight groups from route analysis.
|
|
61
66
|
* @param {object} routeAnalysis Route analysis model.
|
|
67
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
62
68
|
* @returns {object[]}
|
|
63
69
|
*/
|
|
64
|
-
static #routeGroups(routeAnalysis) {
|
|
70
|
+
static #routeGroups(routeAnalysis, routeRowsByName) {
|
|
65
71
|
return [
|
|
66
|
-
...PcbReviewMetadataBuilder.#classGroups(
|
|
67
|
-
|
|
72
|
+
...PcbReviewMetadataBuilder.#classGroups(
|
|
73
|
+
routeAnalysis,
|
|
74
|
+
routeRowsByName
|
|
75
|
+
),
|
|
76
|
+
...PcbReviewMetadataBuilder.#differentialPairGroups(
|
|
77
|
+
routeAnalysis,
|
|
78
|
+
routeRowsByName
|
|
79
|
+
)
|
|
68
80
|
]
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
/**
|
|
72
84
|
* Builds net-class route groups.
|
|
73
85
|
* @param {object} routeAnalysis Route analysis model.
|
|
86
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
74
87
|
* @returns {object[]}
|
|
75
88
|
*/
|
|
76
|
-
static #classGroups(routeAnalysis) {
|
|
89
|
+
static #classGroups(routeAnalysis, routeRowsByName) {
|
|
77
90
|
return (routeAnalysis.classes || []).map((classRow) =>
|
|
78
91
|
PcbReviewMetadataBuilder.#stripEmpty({
|
|
79
92
|
key:
|
|
@@ -83,11 +96,11 @@ export class PcbReviewMetadataBuilder {
|
|
|
83
96
|
name: classRow.name,
|
|
84
97
|
netNames: classRow.netNames || [],
|
|
85
98
|
layerKeys: PcbReviewMetadataBuilder.#layerKeysForNets(
|
|
86
|
-
|
|
99
|
+
routeRowsByName,
|
|
87
100
|
classRow.netNames || []
|
|
88
101
|
),
|
|
89
102
|
primitiveKeys: PcbReviewMetadataBuilder.#primitiveKeysForNets(
|
|
90
|
-
|
|
103
|
+
routeRowsByName,
|
|
91
104
|
classRow.netNames || []
|
|
92
105
|
),
|
|
93
106
|
totalLengthMil: classRow.totalLengthMil
|
|
@@ -98,9 +111,10 @@ export class PcbReviewMetadataBuilder {
|
|
|
98
111
|
/**
|
|
99
112
|
* Builds differential-pair route groups.
|
|
100
113
|
* @param {object} routeAnalysis Route analysis model.
|
|
114
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
101
115
|
* @returns {object[]}
|
|
102
116
|
*/
|
|
103
|
-
static #differentialPairGroups(routeAnalysis) {
|
|
117
|
+
static #differentialPairGroups(routeAnalysis, routeRowsByName) {
|
|
104
118
|
return (routeAnalysis.differentialPairs || []).map((pair) => {
|
|
105
119
|
const netNames = [
|
|
106
120
|
pair.positiveNetName,
|
|
@@ -115,11 +129,11 @@ export class PcbReviewMetadataBuilder {
|
|
|
115
129
|
name: pair.name,
|
|
116
130
|
netNames,
|
|
117
131
|
layerKeys: PcbReviewMetadataBuilder.#layerKeysForNets(
|
|
118
|
-
|
|
132
|
+
routeRowsByName,
|
|
119
133
|
netNames
|
|
120
134
|
),
|
|
121
135
|
primitiveKeys: PcbReviewMetadataBuilder.#primitiveKeysForNets(
|
|
122
|
-
|
|
136
|
+
routeRowsByName,
|
|
123
137
|
netNames
|
|
124
138
|
),
|
|
125
139
|
totalLengthMil: PcbReviewMetadataBuilder.#round(
|
|
@@ -226,20 +240,20 @@ export class PcbReviewMetadataBuilder {
|
|
|
226
240
|
}
|
|
227
241
|
return Object.fromEntries(
|
|
228
242
|
Object.entries(entries).sort(([left], [right]) =>
|
|
229
|
-
|
|
243
|
+
NaturalStringComparator.compare(left, right)
|
|
230
244
|
)
|
|
231
245
|
)
|
|
232
246
|
}
|
|
233
247
|
|
|
234
248
|
/**
|
|
235
249
|
* Returns layer keys participating in a list of nets.
|
|
236
|
-
* @param {object}
|
|
250
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
237
251
|
* @param {string[]} netNames Net names.
|
|
238
252
|
* @returns {string[]}
|
|
239
253
|
*/
|
|
240
|
-
static #layerKeysForNets(
|
|
254
|
+
static #layerKeysForNets(routeRowsByName, netNames) {
|
|
241
255
|
const nets = PcbReviewMetadataBuilder.#netsByName(
|
|
242
|
-
|
|
256
|
+
routeRowsByName,
|
|
243
257
|
netNames
|
|
244
258
|
)
|
|
245
259
|
return PcbReviewMetadataBuilder.#sortedStrings(
|
|
@@ -249,13 +263,13 @@ export class PcbReviewMetadataBuilder {
|
|
|
249
263
|
|
|
250
264
|
/**
|
|
251
265
|
* Returns primitive keys participating in a list of nets.
|
|
252
|
-
* @param {object}
|
|
266
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
253
267
|
* @param {string[]} netNames Net names.
|
|
254
268
|
* @returns {string[]}
|
|
255
269
|
*/
|
|
256
|
-
static #primitiveKeysForNets(
|
|
270
|
+
static #primitiveKeysForNets(routeRowsByName, netNames) {
|
|
257
271
|
const nets = PcbReviewMetadataBuilder.#netsByName(
|
|
258
|
-
|
|
272
|
+
routeRowsByName,
|
|
259
273
|
netNames
|
|
260
274
|
)
|
|
261
275
|
return PcbReviewMetadataBuilder.#sortedStrings(
|
|
@@ -264,15 +278,32 @@ export class PcbReviewMetadataBuilder {
|
|
|
264
278
|
}
|
|
265
279
|
|
|
266
280
|
/**
|
|
267
|
-
*
|
|
281
|
+
* Builds route rows indexed by net name.
|
|
268
282
|
* @param {object} routeAnalysis Route analysis model.
|
|
283
|
+
* @returns {Map<string, object[]>}
|
|
284
|
+
*/
|
|
285
|
+
static #routeRowsByName(routeAnalysis) {
|
|
286
|
+
const rowsByName = new Map()
|
|
287
|
+
for (const net of routeAnalysis.byNet || []) {
|
|
288
|
+
const netName = String(net?.netName || '')
|
|
289
|
+
if (!netName) continue
|
|
290
|
+
if (!rowsByName.has(netName)) {
|
|
291
|
+
rowsByName.set(netName, [])
|
|
292
|
+
}
|
|
293
|
+
rowsByName.get(netName).push(net)
|
|
294
|
+
}
|
|
295
|
+
return rowsByName
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Resolves net route rows by name.
|
|
300
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
269
301
|
* @param {string[]} netNames Net names.
|
|
270
302
|
* @returns {object[]}
|
|
271
303
|
*/
|
|
272
|
-
static #netsByName(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
wanted.has(net.netName)
|
|
304
|
+
static #netsByName(routeRowsByName, netNames) {
|
|
305
|
+
return (netNames || []).flatMap(
|
|
306
|
+
(netName) => routeRowsByName.get(netName) || []
|
|
276
307
|
)
|
|
277
308
|
}
|
|
278
309
|
|
|
@@ -323,10 +354,10 @@ export class PcbReviewMetadataBuilder {
|
|
|
323
354
|
* @returns {string[]}
|
|
324
355
|
*/
|
|
325
356
|
static #sortedStrings(values) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
357
|
+
const sortedValues = [...new Set((values || []).filter(Boolean))]
|
|
358
|
+
return sortedValues.length < 2
|
|
359
|
+
? sortedValues
|
|
360
|
+
: sortedValues.sort(NaturalStringComparator.compare)
|
|
330
361
|
}
|
|
331
362
|
|
|
332
363
|
/**
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
|
|
5
|
+
import { NaturalStringComparator } from './NaturalStringComparator.mjs'
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Builds route-highlight profile rows for PCB review metadata.
|
|
7
9
|
*/
|
|
@@ -12,33 +14,45 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
12
14
|
* @returns {object[]}
|
|
13
15
|
*/
|
|
14
16
|
static build(routeAnalysis = {}) {
|
|
17
|
+
const routeRowsByName =
|
|
18
|
+
PcbReviewRouteHighlightProfileBuilder.#routeRowsByName(
|
|
19
|
+
routeAnalysis
|
|
20
|
+
)
|
|
15
21
|
return [
|
|
16
22
|
...PcbReviewRouteHighlightProfileBuilder.#differentialPairProfiles(
|
|
17
|
-
routeAnalysis
|
|
23
|
+
routeAnalysis,
|
|
24
|
+
routeRowsByName
|
|
18
25
|
),
|
|
19
26
|
...PcbReviewRouteHighlightProfileBuilder.#differentialPairClassProfiles(
|
|
20
|
-
routeAnalysis
|
|
27
|
+
routeAnalysis,
|
|
28
|
+
routeRowsByName
|
|
21
29
|
),
|
|
22
30
|
...PcbReviewRouteHighlightProfileBuilder.#netClassProfiles(
|
|
23
|
-
routeAnalysis
|
|
31
|
+
routeAnalysis,
|
|
32
|
+
routeRowsByName
|
|
24
33
|
),
|
|
25
|
-
...PcbReviewRouteHighlightProfileBuilder.#netProfiles(
|
|
34
|
+
...PcbReviewRouteHighlightProfileBuilder.#netProfiles(
|
|
35
|
+
routeAnalysis,
|
|
36
|
+
routeRowsByName
|
|
37
|
+
)
|
|
26
38
|
]
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
/**
|
|
30
42
|
* Builds net-class highlight profiles.
|
|
31
43
|
* @param {object} routeAnalysis Route analysis model.
|
|
44
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
32
45
|
* @returns {object[]}
|
|
33
46
|
*/
|
|
34
|
-
static #netClassProfiles(routeAnalysis) {
|
|
47
|
+
static #netClassProfiles(routeAnalysis, routeRowsByName) {
|
|
35
48
|
return (routeAnalysis.classes || []).map((classRow) =>
|
|
36
49
|
PcbReviewRouteHighlightProfileBuilder.#highlightProfile({
|
|
37
50
|
selectorKind: 'net-class',
|
|
38
51
|
keyPrefix: 'highlight-net-class-',
|
|
39
52
|
name: classRow.name,
|
|
40
53
|
netNames: classRow.netNames || [],
|
|
41
|
-
routeAnalysis
|
|
54
|
+
routeAnalysis,
|
|
55
|
+
routeRowsByName
|
|
42
56
|
})
|
|
43
57
|
)
|
|
44
58
|
}
|
|
@@ -46,9 +60,10 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
46
60
|
/**
|
|
47
61
|
* Builds differential-pair highlight profiles.
|
|
48
62
|
* @param {object} routeAnalysis Route analysis model.
|
|
63
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
49
64
|
* @returns {object[]}
|
|
50
65
|
*/
|
|
51
|
-
static #differentialPairProfiles(routeAnalysis) {
|
|
66
|
+
static #differentialPairProfiles(routeAnalysis, routeRowsByName) {
|
|
52
67
|
return (routeAnalysis.differentialPairs || []).map((pair) =>
|
|
53
68
|
PcbReviewRouteHighlightProfileBuilder.#highlightProfile({
|
|
54
69
|
selectorKind: 'differential-pair',
|
|
@@ -57,7 +72,8 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
57
72
|
netNames: [pair.positiveNetName, pair.negativeNetName].filter(
|
|
58
73
|
Boolean
|
|
59
74
|
),
|
|
60
|
-
routeAnalysis
|
|
75
|
+
routeAnalysis,
|
|
76
|
+
routeRowsByName
|
|
61
77
|
})
|
|
62
78
|
)
|
|
63
79
|
}
|
|
@@ -65,9 +81,10 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
65
81
|
/**
|
|
66
82
|
* Builds differential-pair class highlight profiles.
|
|
67
83
|
* @param {object} routeAnalysis Route analysis model.
|
|
84
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
68
85
|
* @returns {object[]}
|
|
69
86
|
*/
|
|
70
|
-
static #differentialPairClassProfiles(routeAnalysis) {
|
|
87
|
+
static #differentialPairClassProfiles(routeAnalysis, routeRowsByName) {
|
|
71
88
|
const classNames = new Map()
|
|
72
89
|
for (const pair of routeAnalysis.differentialPairs || []) {
|
|
73
90
|
for (const className of pair.classes || []) {
|
|
@@ -88,7 +105,7 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
88
105
|
|
|
89
106
|
return [...classNames.entries()]
|
|
90
107
|
.sort(([left], [right]) =>
|
|
91
|
-
|
|
108
|
+
NaturalStringComparator.compare(left, right)
|
|
92
109
|
)
|
|
93
110
|
.map(([className, netNames]) =>
|
|
94
111
|
PcbReviewRouteHighlightProfileBuilder.#highlightProfile({
|
|
@@ -96,7 +113,8 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
96
113
|
keyPrefix: 'highlight-diff-pair-class-',
|
|
97
114
|
name: className,
|
|
98
115
|
netNames: [...netNames],
|
|
99
|
-
routeAnalysis
|
|
116
|
+
routeAnalysis,
|
|
117
|
+
routeRowsByName
|
|
100
118
|
})
|
|
101
119
|
)
|
|
102
120
|
}
|
|
@@ -104,23 +122,25 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
104
122
|
/**
|
|
105
123
|
* Builds scalar net highlight profiles.
|
|
106
124
|
* @param {object} routeAnalysis Route analysis model.
|
|
125
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
107
126
|
* @returns {object[]}
|
|
108
127
|
*/
|
|
109
|
-
static #netProfiles(routeAnalysis) {
|
|
128
|
+
static #netProfiles(routeAnalysis, routeRowsByName) {
|
|
110
129
|
return (routeAnalysis.byNet || []).map((net) =>
|
|
111
130
|
PcbReviewRouteHighlightProfileBuilder.#highlightProfile({
|
|
112
131
|
selectorKind: 'net',
|
|
113
132
|
keyPrefix: 'highlight-net-',
|
|
114
133
|
name: net.netName,
|
|
115
134
|
netNames: [net.netName],
|
|
116
|
-
routeAnalysis
|
|
135
|
+
routeAnalysis,
|
|
136
|
+
routeRowsByName
|
|
117
137
|
})
|
|
118
138
|
)
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
/**
|
|
122
142
|
* Builds one route-highlight profile.
|
|
123
|
-
* @param {{ selectorKind: string, keyPrefix: string, name: string, netNames: string[], routeAnalysis: object }} options Profile options.
|
|
143
|
+
* @param {{ selectorKind: string, keyPrefix: string, name: string, netNames: string[], routeAnalysis: object, routeRowsByName: Map<string, object[]> }} options Profile options.
|
|
124
144
|
* @returns {object}
|
|
125
145
|
*/
|
|
126
146
|
static #highlightProfile(options) {
|
|
@@ -129,7 +149,8 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
129
149
|
)
|
|
130
150
|
const layerGroups = PcbReviewRouteHighlightProfileBuilder.#layerGroups(
|
|
131
151
|
options.routeAnalysis,
|
|
132
|
-
netNames
|
|
152
|
+
netNames,
|
|
153
|
+
options.routeRowsByName
|
|
133
154
|
)
|
|
134
155
|
|
|
135
156
|
return PcbReviewRouteHighlightProfileBuilder.#stripEmpty({
|
|
@@ -158,12 +179,13 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
158
179
|
* Builds per-layer route-highlight groups.
|
|
159
180
|
* @param {object} routeAnalysis Route analysis model.
|
|
160
181
|
* @param {string[]} netNames Net names.
|
|
182
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
161
183
|
* @returns {object[]}
|
|
162
184
|
*/
|
|
163
|
-
static #layerGroups(routeAnalysis, netNames) {
|
|
185
|
+
static #layerGroups(routeAnalysis, netNames, routeRowsByName) {
|
|
164
186
|
const groupsByLayer = new Map()
|
|
165
187
|
for (const net of PcbReviewRouteHighlightProfileBuilder.#netsByName(
|
|
166
|
-
|
|
188
|
+
routeRowsByName,
|
|
167
189
|
netNames
|
|
168
190
|
)) {
|
|
169
191
|
for (const participation of net.layerParticipation || []) {
|
|
@@ -205,22 +227,37 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
205
227
|
)
|
|
206
228
|
}))
|
|
207
229
|
.sort((left, right) =>
|
|
208
|
-
left.layerKey
|
|
209
|
-
numeric: true
|
|
210
|
-
})
|
|
230
|
+
NaturalStringComparator.compare(left.layerKey, right.layerKey)
|
|
211
231
|
)
|
|
212
232
|
}
|
|
213
233
|
|
|
214
234
|
/**
|
|
215
|
-
*
|
|
235
|
+
* Builds route rows indexed by net name.
|
|
216
236
|
* @param {object} routeAnalysis Route analysis model.
|
|
237
|
+
* @returns {Map<string, object[]>}
|
|
238
|
+
*/
|
|
239
|
+
static #routeRowsByName(routeAnalysis) {
|
|
240
|
+
const rowsByName = new Map()
|
|
241
|
+
for (const net of routeAnalysis.byNet || []) {
|
|
242
|
+
const netName = String(net?.netName || '')
|
|
243
|
+
if (!netName) continue
|
|
244
|
+
if (!rowsByName.has(netName)) {
|
|
245
|
+
rowsByName.set(netName, [])
|
|
246
|
+
}
|
|
247
|
+
rowsByName.get(netName).push(net)
|
|
248
|
+
}
|
|
249
|
+
return rowsByName
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Resolves net route rows by name.
|
|
254
|
+
* @param {Map<string, object[]>} routeRowsByName Route rows by net name.
|
|
217
255
|
* @param {string[]} netNames Net names.
|
|
218
256
|
* @returns {object[]}
|
|
219
257
|
*/
|
|
220
|
-
static #netsByName(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
wanted.has(net.netName)
|
|
258
|
+
static #netsByName(routeRowsByName, netNames) {
|
|
259
|
+
return (netNames || []).flatMap(
|
|
260
|
+
(netName) => routeRowsByName.get(netName) || []
|
|
224
261
|
)
|
|
225
262
|
}
|
|
226
263
|
|
|
@@ -248,10 +285,10 @@ export class PcbReviewRouteHighlightProfileBuilder {
|
|
|
248
285
|
* @returns {string[]}
|
|
249
286
|
*/
|
|
250
287
|
static #sortedStrings(values) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
288
|
+
const sortedValues = [...new Set((values || []).filter(Boolean))]
|
|
289
|
+
return sortedValues.length < 2
|
|
290
|
+
? sortedValues
|
|
291
|
+
: sortedValues.sort(NaturalStringComparator.compare)
|
|
255
292
|
}
|
|
256
293
|
|
|
257
294
|
/**
|
|
@@ -183,23 +183,30 @@ export class PcbRouteAnalysisBuilder {
|
|
|
183
183
|
* @returns {object[]}
|
|
184
184
|
*/
|
|
185
185
|
static #netRows(pcb, routePrimitives, viaRows) {
|
|
186
|
+
const primitivesByNet =
|
|
187
|
+
PcbRouteAnalysisBuilder.#rowsByNet(routePrimitives)
|
|
188
|
+
const viasByNet = PcbRouteAnalysisBuilder.#rowsByNet(viaRows)
|
|
186
189
|
const netNames = new Set([
|
|
187
190
|
...(pcb?.nets || []).map((net) => net.name).filter(Boolean),
|
|
188
|
-
...
|
|
189
|
-
...
|
|
191
|
+
...primitivesByNet.keys(),
|
|
192
|
+
...viasByNet.keys()
|
|
190
193
|
])
|
|
191
194
|
|
|
192
195
|
return [...netNames]
|
|
193
|
-
.map((netName) =>
|
|
194
|
-
|
|
196
|
+
.map((netName) => {
|
|
197
|
+
const primitives = primitivesByNet.get(netName) || []
|
|
198
|
+
const vias = viasByNet.get(netName) || []
|
|
199
|
+
if (!primitives.length && !vias.length) {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return PcbRouteAnalysisBuilder.#netRow(
|
|
195
204
|
netName,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
),
|
|
199
|
-
viaRows.filter((via) => via.netName === netName)
|
|
205
|
+
primitives,
|
|
206
|
+
vias
|
|
200
207
|
)
|
|
201
|
-
)
|
|
202
|
-
.filter(
|
|
208
|
+
})
|
|
209
|
+
.filter(Boolean)
|
|
203
210
|
.sort((left, right) =>
|
|
204
211
|
left.netName.localeCompare(right.netName, undefined, {
|
|
205
212
|
numeric: true
|
|
@@ -215,12 +222,16 @@ export class PcbRouteAnalysisBuilder {
|
|
|
215
222
|
* @returns {object}
|
|
216
223
|
*/
|
|
217
224
|
static #netRow(netName, primitives, vias) {
|
|
218
|
-
const trackRows =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
(primitive
|
|
223
|
-
|
|
225
|
+
const trackRows = []
|
|
226
|
+
const arcRows = []
|
|
227
|
+
|
|
228
|
+
for (const primitive of primitives || []) {
|
|
229
|
+
if (primitive.kind === 'track') {
|
|
230
|
+
trackRows.push(primitive)
|
|
231
|
+
} else if (primitive.kind === 'arc') {
|
|
232
|
+
arcRows.push(primitive)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
224
235
|
|
|
225
236
|
return {
|
|
226
237
|
netName,
|
|
@@ -240,6 +251,29 @@ export class PcbRouteAnalysisBuilder {
|
|
|
240
251
|
}
|
|
241
252
|
}
|
|
242
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Groups route rows by net name.
|
|
256
|
+
* @param {object[]} rows Route or via rows.
|
|
257
|
+
* @returns {Map<string, object[]>}
|
|
258
|
+
*/
|
|
259
|
+
static #rowsByNet(rows) {
|
|
260
|
+
const rowsByNet = new Map()
|
|
261
|
+
|
|
262
|
+
for (const row of rows || []) {
|
|
263
|
+
const netName = String(row?.netName || '').trim()
|
|
264
|
+
if (!netName) {
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!rowsByNet.has(netName)) {
|
|
269
|
+
rowsByNet.set(netName, [])
|
|
270
|
+
}
|
|
271
|
+
rowsByNet.get(netName).push(row)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return rowsByNet
|
|
275
|
+
}
|
|
276
|
+
|
|
243
277
|
/**
|
|
244
278
|
* Builds per-net-class length summaries.
|
|
245
279
|
* @param {object} pcb Normalized PCB model.
|
|
@@ -18,12 +18,14 @@ export class SchematicDisplayModeCatalogParser {
|
|
|
18
18
|
* @returns {object | null}
|
|
19
19
|
*/
|
|
20
20
|
static parse(records) {
|
|
21
|
+
const childrenByOwner =
|
|
22
|
+
SchematicDisplayModeCatalogParser.#ownerChildrenByOwner(records)
|
|
21
23
|
const components = (records || [])
|
|
22
24
|
.filter((record) => getField(record.fields, 'RECORD') === '1')
|
|
23
25
|
.map((record) =>
|
|
24
26
|
SchematicDisplayModeCatalogParser.#componentCatalog(
|
|
25
27
|
record,
|
|
26
|
-
|
|
28
|
+
childrenByOwner
|
|
27
29
|
)
|
|
28
30
|
)
|
|
29
31
|
.filter(Boolean)
|
|
@@ -41,17 +43,17 @@ export class SchematicDisplayModeCatalogParser {
|
|
|
41
43
|
/**
|
|
42
44
|
* Builds one component display-mode catalog row.
|
|
43
45
|
* @param {object} componentRecord Component record.
|
|
44
|
-
* @param {object[]} records
|
|
46
|
+
* @param {Map<string, object[]>} childrenByOwner Child records by owner index.
|
|
45
47
|
* @returns {object}
|
|
46
48
|
*/
|
|
47
|
-
static #componentCatalog(componentRecord,
|
|
49
|
+
static #componentCatalog(componentRecord, childrenByOwner) {
|
|
48
50
|
const indexInSheet = parseNumericField(
|
|
49
51
|
componentRecord.fields,
|
|
50
52
|
'IndexInSheet'
|
|
51
53
|
)
|
|
52
54
|
const ownerIndex = String(indexInSheet ?? componentRecord.recordIndex)
|
|
53
55
|
const children = SchematicDisplayModeCatalogParser.#ownerChildren(
|
|
54
|
-
|
|
56
|
+
childrenByOwner,
|
|
55
57
|
ownerIndex
|
|
56
58
|
)
|
|
57
59
|
const declaredPartCount =
|
|
@@ -95,16 +97,39 @@ export class SchematicDisplayModeCatalogParser {
|
|
|
95
97
|
|
|
96
98
|
/**
|
|
97
99
|
* Returns child records with owner part/display metadata.
|
|
98
|
-
* @param {object[]} records
|
|
100
|
+
* @param {Map<string, object[]>} childrenByOwner Child records by owner index.
|
|
99
101
|
* @param {string} ownerIndex Component owner index.
|
|
100
102
|
* @returns {object[]}
|
|
101
103
|
*/
|
|
102
|
-
static #ownerChildren(
|
|
103
|
-
return (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
static #ownerChildren(childrenByOwner, ownerIndex) {
|
|
105
|
+
return childrenByOwner.get(ownerIndex) || []
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Indexes schematic owner child records once for display-mode cataloguing.
|
|
110
|
+
* @param {object[]} records Schematic records.
|
|
111
|
+
* @returns {Map<string, object[]>}
|
|
112
|
+
*/
|
|
113
|
+
static #ownerChildrenByOwner(records) {
|
|
114
|
+
const childrenByOwner = new Map()
|
|
115
|
+
|
|
116
|
+
for (const record of records || []) {
|
|
117
|
+
if (parseNumericField(record.fields, 'OwnerPartID') === null) {
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const ownerIndex = getField(record.fields, 'OwnerIndex')
|
|
122
|
+
if (!ownerIndex) {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!childrenByOwner.has(ownerIndex)) {
|
|
127
|
+
childrenByOwner.set(ownerIndex, [])
|
|
128
|
+
}
|
|
129
|
+
childrenByOwner.get(ownerIndex).push(record)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return childrenByOwner
|
|
108
133
|
}
|
|
109
134
|
|
|
110
135
|
/**
|