circuitjson-toolkit 1.0.10 → 1.0.16
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/AGENTS.md +5 -3
- package/docs/model-format.md +15 -0
- package/package.json +3 -2
- package/src/core/CircuitJsonBomBuilder.mjs +22 -25
- package/src/core/CircuitJsonElementValidator.mjs +233 -16
- package/src/core/CircuitJsonIndexer.mjs +510 -5
- package/src/core/CircuitJsonManufacturingBuilder.mjs +488 -16
- package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
- package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
- package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
- package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
- package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
- package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
- package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +227 -5
- package/src/core/PcbBoundsSelectionModel.mjs +250 -0
- package/src/core/PcbCandidateSelectionModel.mjs +77 -0
- package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
- package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
- package/src/index.mjs +2 -0
- package/src/renderers.mjs +29 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
package/AGENTS.md
CHANGED
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
## Library Scope
|
|
35
35
|
|
|
36
36
|
- Include CircuitJSON element-array validation, parsing, indexing, unit helpers,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
deterministic renderer-neutral primitive helpers, deterministic SVG renderers,
|
|
38
|
+
and small summary/export helpers.
|
|
39
|
+
- Do not include Three.js code, ECAD parser logic, UI wiring, host app state,
|
|
40
|
+
DOM event orchestration, download UI, or source-format-specific compatibility
|
|
41
|
+
adapters.
|
|
40
42
|
|
|
41
43
|
## Testing Guidelines
|
|
42
44
|
|
package/docs/model-format.md
CHANGED
|
@@ -63,6 +63,21 @@ Indexed element IDs are stored with a type prefix, such as
|
|
|
63
63
|
`pcb_board:board_1`, so different element classes can reuse local IDs without
|
|
64
64
|
colliding.
|
|
65
65
|
|
|
66
|
+
PCB courtyard artwork rows use the normal `type_id` identifier convention. The
|
|
67
|
+
validator accepts generic `pcb_courtyard` rows plus shape-specific variants such
|
|
68
|
+
as `pcb_courtyard_rect`, `pcb_courtyard_circle`, `pcb_courtyard_outline`,
|
|
69
|
+
`pcb_courtyard_path`, and `pcb_courtyard_line`.
|
|
70
|
+
|
|
71
|
+
## Group Indexing
|
|
72
|
+
|
|
73
|
+
`CircuitJsonIndexer` builds `groupsById` and `elementsByGroupId` from direct
|
|
74
|
+
group fields such as `source_group_id`, `pcb_group_id`,
|
|
75
|
+
`schematic_group_id`, `group_id`, and `group_ids`. It also treats member-style
|
|
76
|
+
fields such as `member_source_group_ids`, `member_pcb_group_ids`,
|
|
77
|
+
`member_schematic_group_ids`, and `member_group_ids` as group memberships, so
|
|
78
|
+
source nets and other non-group elements can participate in group indexes
|
|
79
|
+
without duplicating direct group fields.
|
|
80
|
+
|
|
66
81
|
## Units
|
|
67
82
|
|
|
68
83
|
CircuitJSON PCB coordinates and dimensions are millimeter-based. The toolkit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuitjson-toolkit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "Dependency-free CircuitJSON parsing, validation, indexing, and utility helpers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"circuitjson",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"main": "./src/index.mjs",
|
|
29
29
|
"exports": {
|
|
30
30
|
".": "./src/index.mjs",
|
|
31
|
-
"./parser": "./src/parser.mjs"
|
|
31
|
+
"./parser": "./src/parser.mjs",
|
|
32
|
+
"./renderers": "./src/renderers.mjs"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
34
35
|
"src",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CircuitJsonSourceMetadata } from './CircuitJsonSourceMetadata.mjs'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Builds app-compatible BOM rows from source component elements.
|
|
3
5
|
*/
|
|
@@ -14,16 +16,24 @@ export class CircuitJsonBomBuilder {
|
|
|
14
16
|
const designator = CircuitJsonBomBuilder.#designator(component)
|
|
15
17
|
if (!designator) continue
|
|
16
18
|
|
|
17
|
-
const
|
|
19
|
+
const metadata =
|
|
20
|
+
CircuitJsonSourceMetadata.normalizeSourceComponent(component)
|
|
21
|
+
const key = CircuitJsonBomBuilder.#groupKey(component, metadata)
|
|
18
22
|
if (!groups.has(key)) {
|
|
19
23
|
groups.set(key, {
|
|
20
24
|
designators: [],
|
|
21
25
|
quantity: 0,
|
|
22
26
|
value: CircuitJsonBomBuilder.#value(component),
|
|
23
|
-
pattern: CircuitJsonBomBuilder.#pattern(
|
|
27
|
+
pattern: CircuitJsonBomBuilder.#pattern(
|
|
28
|
+
component,
|
|
29
|
+
metadata
|
|
30
|
+
),
|
|
24
31
|
source: CircuitJsonBomBuilder.#source(component),
|
|
25
|
-
supplierPartNumber:
|
|
26
|
-
|
|
32
|
+
supplierPartNumber: metadata.supplierPartNumber,
|
|
33
|
+
supplierPartNumbers: metadata.supplierPartNumbers,
|
|
34
|
+
sourceFtype: metadata.sourceFtype,
|
|
35
|
+
componentType: metadata.componentType,
|
|
36
|
+
componentIcon: metadata.componentIcon
|
|
27
37
|
})
|
|
28
38
|
}
|
|
29
39
|
const row = groups.get(key)
|
|
@@ -57,14 +67,16 @@ export class CircuitJsonBomBuilder {
|
|
|
57
67
|
/**
|
|
58
68
|
* Builds a stable grouping key from BOM-facing fields.
|
|
59
69
|
* @param {object} component Source component.
|
|
70
|
+
* @param {object} metadata Normalized source metadata.
|
|
60
71
|
* @returns {string}
|
|
61
72
|
*/
|
|
62
|
-
static #groupKey(component) {
|
|
73
|
+
static #groupKey(component, metadata) {
|
|
63
74
|
return [
|
|
64
75
|
CircuitJsonBomBuilder.#value(component),
|
|
65
|
-
CircuitJsonBomBuilder.#pattern(component),
|
|
76
|
+
CircuitJsonBomBuilder.#pattern(component, metadata),
|
|
66
77
|
CircuitJsonBomBuilder.#source(component),
|
|
67
|
-
|
|
78
|
+
metadata.supplierPartNumber,
|
|
79
|
+
metadata.sourceFtype
|
|
68
80
|
].join('\u001f')
|
|
69
81
|
}
|
|
70
82
|
|
|
@@ -88,14 +100,15 @@ export class CircuitJsonBomBuilder {
|
|
|
88
100
|
/**
|
|
89
101
|
* Resolves a footprint/package pattern label.
|
|
90
102
|
* @param {object} component Source component.
|
|
103
|
+
* @param {object} metadata Normalized source metadata.
|
|
91
104
|
* @returns {string}
|
|
92
105
|
*/
|
|
93
|
-
static #pattern(component) {
|
|
106
|
+
static #pattern(component, metadata) {
|
|
94
107
|
return String(
|
|
95
108
|
component.footprint ??
|
|
96
109
|
component.package ??
|
|
97
110
|
component.package_name ??
|
|
98
|
-
|
|
111
|
+
metadata.sourceFtype ??
|
|
99
112
|
''
|
|
100
113
|
).trim()
|
|
101
114
|
}
|
|
@@ -115,22 +128,6 @@ export class CircuitJsonBomBuilder {
|
|
|
115
128
|
).trim()
|
|
116
129
|
}
|
|
117
130
|
|
|
118
|
-
/**
|
|
119
|
-
* Resolves a supplier part number.
|
|
120
|
-
* @param {object} component Source component.
|
|
121
|
-
* @returns {string}
|
|
122
|
-
*/
|
|
123
|
-
static #supplierPartNumber(component) {
|
|
124
|
-
if (component.supplier_part_number) {
|
|
125
|
-
return String(component.supplier_part_number).trim()
|
|
126
|
-
}
|
|
127
|
-
const numbers = component.supplier_part_numbers
|
|
128
|
-
if (!numbers || typeof numbers !== 'object' || Array.isArray(numbers)) {
|
|
129
|
-
return ''
|
|
130
|
-
}
|
|
131
|
-
return String(Object.values(numbers).find(Boolean) || '').trim()
|
|
132
|
-
}
|
|
133
|
-
|
|
134
131
|
/**
|
|
135
132
|
* Compares designators using natural ordering.
|
|
136
133
|
* @param {string} left Left designator.
|
|
@@ -16,7 +16,9 @@ const KNOWN_ELEMENT_TYPES = new Set([
|
|
|
16
16
|
'pcb_copper_text',
|
|
17
17
|
'pcb_courtyard',
|
|
18
18
|
'pcb_courtyard_circle',
|
|
19
|
+
'pcb_courtyard_line',
|
|
19
20
|
'pcb_courtyard_outline',
|
|
21
|
+
'pcb_courtyard_path',
|
|
20
22
|
'pcb_courtyard_overlap_error',
|
|
21
23
|
'pcb_courtyard_pill',
|
|
22
24
|
'pcb_courtyard_polygon',
|
|
@@ -111,7 +113,6 @@ const KNOWN_ELEMENT_TYPES = new Set([
|
|
|
111
113
|
'source_failed_to_create_component_error',
|
|
112
114
|
'source_group',
|
|
113
115
|
'source_i2c_misconfigured_error',
|
|
114
|
-
'source_interconnect',
|
|
115
116
|
'source_invalid_component_property_error',
|
|
116
117
|
'source_manually_placed_via',
|
|
117
118
|
'source_missing_manufacturer_part_number_warning',
|
|
@@ -143,6 +144,7 @@ const ID_FIELD_EXCEPTIONS = new Set([
|
|
|
143
144
|
])
|
|
144
145
|
|
|
145
146
|
const SOURCE_COMPONENT_FTYPES = new Set([
|
|
147
|
+
'interconnect',
|
|
146
148
|
'simple_ammeter',
|
|
147
149
|
'simple_battery',
|
|
148
150
|
'simple_capacitor',
|
|
@@ -172,16 +174,9 @@ const SOURCE_COMPONENT_FTYPES = new Set([
|
|
|
172
174
|
'simple_voltage_source'
|
|
173
175
|
])
|
|
174
176
|
|
|
175
|
-
const LAYERS = new Set(
|
|
176
|
-
'top'
|
|
177
|
-
|
|
178
|
-
'inner1',
|
|
179
|
-
'inner2',
|
|
180
|
-
'inner3',
|
|
181
|
-
'inner4',
|
|
182
|
-
'inner5',
|
|
183
|
-
'inner6'
|
|
184
|
-
])
|
|
177
|
+
const LAYERS = new Set(
|
|
178
|
+
'top bottom inner1 inner2 inner3 inner4 inner5 inner6'.split(' ')
|
|
179
|
+
)
|
|
185
180
|
|
|
186
181
|
const SMT_PAD_SHAPES = new Set([
|
|
187
182
|
'circle',
|
|
@@ -189,9 +184,88 @@ const SMT_PAD_SHAPES = new Set([
|
|
|
189
184
|
'rotated_rect',
|
|
190
185
|
'rotated_pill',
|
|
191
186
|
'pill',
|
|
192
|
-
'polygon'
|
|
187
|
+
'polygon',
|
|
188
|
+
'rounded_rect'
|
|
189
|
+
])
|
|
190
|
+
|
|
191
|
+
const PCB_HOLE_SHAPES = new Set([
|
|
192
|
+
'circle',
|
|
193
|
+
'circle_or_square',
|
|
194
|
+
'oval',
|
|
195
|
+
'pill',
|
|
196
|
+
'rect',
|
|
197
|
+
'rotated_pill',
|
|
198
|
+
'round'
|
|
193
199
|
])
|
|
194
200
|
|
|
201
|
+
const PCB_PLATED_HOLE_SHAPES = new Set([
|
|
202
|
+
'circle',
|
|
203
|
+
'circular_hole_with_rect_pad',
|
|
204
|
+
'hole_with_polygon_pad',
|
|
205
|
+
'oval',
|
|
206
|
+
'pill',
|
|
207
|
+
'pill_hole_with_rect_pad',
|
|
208
|
+
'rotated_pill_hole_with_rect_pad'
|
|
209
|
+
])
|
|
210
|
+
|
|
211
|
+
const PCB_PLATED_HOLE_HOLE_SHAPES = new Set([
|
|
212
|
+
'circle',
|
|
213
|
+
'oval',
|
|
214
|
+
'pill',
|
|
215
|
+
'rotated_pill'
|
|
216
|
+
])
|
|
217
|
+
|
|
218
|
+
const PCB_SOLDER_PASTE_SHAPES = new Set([
|
|
219
|
+
'circle',
|
|
220
|
+
'oval',
|
|
221
|
+
'pill',
|
|
222
|
+
'rect',
|
|
223
|
+
'rotated_rect'
|
|
224
|
+
])
|
|
225
|
+
|
|
226
|
+
const PCB_BOARD_SHAPES = new Set(['polygon', 'rect'])
|
|
227
|
+
|
|
228
|
+
const PCB_CUTOUT_SHAPES = new Set(['circle', 'path', 'polygon', 'rect'])
|
|
229
|
+
|
|
230
|
+
const PCB_COPPER_POUR_SHAPES = new Set(['brep', 'polygon', 'rect'])
|
|
231
|
+
|
|
232
|
+
const SIMULATION_SOURCE_KINDS = new Set(['ac', 'dc'])
|
|
233
|
+
|
|
234
|
+
const SIMULATION_WAVE_SHAPES = new Set(
|
|
235
|
+
'sawtooth sinewave square triangle'.split(' ')
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
const SIMULATION_EXPERIMENT_METHODS = new Set(['gear', 'trap'])
|
|
239
|
+
|
|
240
|
+
const VARIANT_SETS = {
|
|
241
|
+
sourceComponentFtypes: SOURCE_COMPONENT_FTYPES,
|
|
242
|
+
pcbBoardShapes: PCB_BOARD_SHAPES,
|
|
243
|
+
pcbSmtPadShapes: SMT_PAD_SHAPES,
|
|
244
|
+
pcbHoleShapes: PCB_HOLE_SHAPES,
|
|
245
|
+
pcbPlatedHoleShapes: PCB_PLATED_HOLE_SHAPES,
|
|
246
|
+
pcbPlatedHoleHoleShapes: PCB_PLATED_HOLE_HOLE_SHAPES,
|
|
247
|
+
pcbSolderPasteShapes: PCB_SOLDER_PASTE_SHAPES,
|
|
248
|
+
pcbCutoutShapes: PCB_CUTOUT_SHAPES,
|
|
249
|
+
pcbCopperPourShapes: PCB_COPPER_POUR_SHAPES,
|
|
250
|
+
simulationSourceKinds: SIMULATION_SOURCE_KINDS,
|
|
251
|
+
simulationWaveShapes: SIMULATION_WAVE_SHAPES,
|
|
252
|
+
simulationExperimentMethods: SIMULATION_EXPERIMENT_METHODS
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const SORTED_VARIANT_SET_NAMES = Object.keys(VARIANT_SETS).sort()
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @typedef {{ set: string, value: string }} CircuitJsonVariantDiff
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @typedef {{ elementTypes: string[], idFieldExceptions: string[], variantSets: Record<string, string[]> }} CircuitJsonSchemaSnapshot
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @typedef {{ matches: boolean, missingElementTypes: string[], unexpectedElementTypes: string[], missingIdFieldExceptions: string[], unexpectedIdFieldExceptions: string[], missingVariants: CircuitJsonVariantDiff[], unexpectedVariants: CircuitJsonVariantDiff[] }} CircuitJsonSchemaSnapshotComparison
|
|
267
|
+
*/
|
|
268
|
+
|
|
195
269
|
/**
|
|
196
270
|
* Validates serialized CircuitJSON element objects without external runtime
|
|
197
271
|
* dependencies.
|
|
@@ -255,10 +329,35 @@ export class CircuitJsonElementValidator {
|
|
|
255
329
|
return [...ID_FIELD_EXCEPTIONS]
|
|
256
330
|
}
|
|
257
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Returns schema metadata suitable for drift snapshots.
|
|
334
|
+
* @returns {CircuitJsonSchemaSnapshot}
|
|
335
|
+
*/
|
|
336
|
+
static schemaSnapshot() {
|
|
337
|
+
return {
|
|
338
|
+
elementTypes: CircuitJsonElementValidator.knownElementTypes(),
|
|
339
|
+
idFieldExceptions: CircuitJsonElementValidator.idFieldExceptions(),
|
|
340
|
+
variantSets: CircuitJsonElementValidator.variantSets()
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Returns known variant discriminants from the active schema metadata.
|
|
346
|
+
* @returns {Record<string, string[]>}
|
|
347
|
+
*/
|
|
348
|
+
static variantSets() {
|
|
349
|
+
return Object.fromEntries(
|
|
350
|
+
SORTED_VARIANT_SET_NAMES.map((name) => [
|
|
351
|
+
name,
|
|
352
|
+
[...VARIANT_SETS[name]].sort()
|
|
353
|
+
])
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
258
357
|
/**
|
|
259
358
|
* Compares current schema metadata against a saved snapshot.
|
|
260
|
-
* @param {{ elementTypes?: string[], idFieldExceptions?: string[] }} snapshot Schema snapshot.
|
|
261
|
-
* @returns {
|
|
359
|
+
* @param {{ elementTypes?: string[], idFieldExceptions?: string[], variantSets?: Record<string, string[]> }} snapshot Schema snapshot.
|
|
360
|
+
* @returns {CircuitJsonSchemaSnapshotComparison}
|
|
262
361
|
*/
|
|
263
362
|
static compareSchemaSnapshot(snapshot = {}) {
|
|
264
363
|
const elementComparison = CircuitJsonElementValidator.#compareSets(
|
|
@@ -269,17 +368,25 @@ export class CircuitJsonElementValidator {
|
|
|
269
368
|
new Set(snapshot.idFieldExceptions || []),
|
|
270
369
|
ID_FIELD_EXCEPTIONS
|
|
271
370
|
)
|
|
371
|
+
const variantComparison =
|
|
372
|
+
CircuitJsonElementValidator.#compareVariantSets(
|
|
373
|
+
snapshot.variantSets || {}
|
|
374
|
+
)
|
|
272
375
|
|
|
273
376
|
return {
|
|
274
377
|
matches:
|
|
275
378
|
elementComparison.missing.length === 0 &&
|
|
276
379
|
elementComparison.unexpected.length === 0 &&
|
|
277
380
|
exceptionComparison.missing.length === 0 &&
|
|
278
|
-
exceptionComparison.unexpected.length === 0
|
|
381
|
+
exceptionComparison.unexpected.length === 0 &&
|
|
382
|
+
variantComparison.missing.length === 0 &&
|
|
383
|
+
variantComparison.unexpected.length === 0,
|
|
279
384
|
missingElementTypes: elementComparison.missing,
|
|
280
385
|
unexpectedElementTypes: elementComparison.unexpected,
|
|
281
386
|
missingIdFieldExceptions: exceptionComparison.missing,
|
|
282
|
-
unexpectedIdFieldExceptions: exceptionComparison.unexpected
|
|
387
|
+
unexpectedIdFieldExceptions: exceptionComparison.unexpected,
|
|
388
|
+
missingVariants: variantComparison.missing,
|
|
389
|
+
unexpectedVariants: variantComparison.unexpected
|
|
283
390
|
}
|
|
284
391
|
}
|
|
285
392
|
|
|
@@ -342,6 +449,14 @@ export class CircuitJsonElementValidator {
|
|
|
342
449
|
|
|
343
450
|
if (type === 'pcb_smtpad') {
|
|
344
451
|
CircuitJsonElementValidator.#validatePcbSmtPad(element, errors)
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (type === 'simulation_oscilloscope_trace') {
|
|
456
|
+
CircuitJsonElementValidator.#validateSimulationOscilloscopeTrace(
|
|
457
|
+
element,
|
|
458
|
+
errors
|
|
459
|
+
)
|
|
345
460
|
}
|
|
346
461
|
}
|
|
347
462
|
|
|
@@ -566,6 +681,55 @@ export class CircuitJsonElementValidator {
|
|
|
566
681
|
}
|
|
567
682
|
}
|
|
568
683
|
|
|
684
|
+
/**
|
|
685
|
+
* Validates oscilloscope trace reference and unit constraints.
|
|
686
|
+
* @param {Record<string, unknown>} element Element.
|
|
687
|
+
* @param {string[]} errors Error sink.
|
|
688
|
+
* @returns {void}
|
|
689
|
+
*/
|
|
690
|
+
static #validateSimulationOscilloscopeTrace(element, errors) {
|
|
691
|
+
const voltageReferences = [
|
|
692
|
+
element.simulation_transient_voltage_graph_id,
|
|
693
|
+
element.simulation_voltage_probe_id
|
|
694
|
+
].filter((value) =>
|
|
695
|
+
CircuitJsonElementValidator.#isNonEmptyString(value)
|
|
696
|
+
).length
|
|
697
|
+
const currentReferences = [
|
|
698
|
+
element.simulation_transient_current_graph_id,
|
|
699
|
+
element.simulation_current_probe_id
|
|
700
|
+
].filter((value) =>
|
|
701
|
+
CircuitJsonElementValidator.#isNonEmptyString(value)
|
|
702
|
+
).length
|
|
703
|
+
|
|
704
|
+
if (voltageReferences + currentReferences !== 1) {
|
|
705
|
+
errors.push(
|
|
706
|
+
'simulation_oscilloscope_trace must reference exactly one graph or probe.'
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
if (voltageReferences > 0 && Object.hasOwn(element, 'amps_per_div')) {
|
|
710
|
+
errors.push(
|
|
711
|
+
'voltage oscilloscope traces must use volts_per_div, not amps_per_div.'
|
|
712
|
+
)
|
|
713
|
+
}
|
|
714
|
+
if (currentReferences > 0 && Object.hasOwn(element, 'volts_per_div')) {
|
|
715
|
+
errors.push(
|
|
716
|
+
'current oscilloscope traces must use amps_per_div, not volts_per_div.'
|
|
717
|
+
)
|
|
718
|
+
}
|
|
719
|
+
CircuitJsonElementValidator.#optionalPositiveNumber(
|
|
720
|
+
element,
|
|
721
|
+
'simulation_oscilloscope_trace',
|
|
722
|
+
'volts_per_div',
|
|
723
|
+
errors
|
|
724
|
+
)
|
|
725
|
+
CircuitJsonElementValidator.#optionalPositiveNumber(
|
|
726
|
+
element,
|
|
727
|
+
'simulation_oscilloscope_trace',
|
|
728
|
+
'amps_per_div',
|
|
729
|
+
errors
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
|
|
569
733
|
/**
|
|
570
734
|
* Requires a non-empty string field.
|
|
571
735
|
* @param {Record<string, unknown>} element Element.
|
|
@@ -666,6 +830,24 @@ export class CircuitJsonElementValidator {
|
|
|
666
830
|
}
|
|
667
831
|
}
|
|
668
832
|
|
|
833
|
+
/**
|
|
834
|
+
* Validates an optional positive finite number field.
|
|
835
|
+
* @param {Record<string, unknown>} element Element.
|
|
836
|
+
* @param {string} type Element type.
|
|
837
|
+
* @param {string} field Field name.
|
|
838
|
+
* @param {string[]} errors Error sink.
|
|
839
|
+
* @returns {void}
|
|
840
|
+
*/
|
|
841
|
+
static #optionalPositiveNumber(element, type, field, errors) {
|
|
842
|
+
if (!Object.hasOwn(element, field)) {
|
|
843
|
+
return
|
|
844
|
+
}
|
|
845
|
+
const value = Number(element[field])
|
|
846
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
847
|
+
errors.push(type + ' ' + field + ' must be a positive number.')
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
669
851
|
/**
|
|
670
852
|
* Requires a finite point object field.
|
|
671
853
|
* @param {Record<string, unknown>} element Element.
|
|
@@ -770,4 +952,39 @@ export class CircuitJsonElementValidator {
|
|
|
770
952
|
.sort((left, right) => left.localeCompare(right))
|
|
771
953
|
}
|
|
772
954
|
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Compares expected and actual variant metadata sets.
|
|
958
|
+
* @param {Record<string, string[]>} snapshotSets Snapshot variant sets.
|
|
959
|
+
* @returns {{ missing: CircuitJsonVariantDiff[], unexpected: CircuitJsonVariantDiff[] }}
|
|
960
|
+
*/
|
|
961
|
+
static #compareVariantSets(snapshotSets) {
|
|
962
|
+
const missing = []
|
|
963
|
+
const unexpected = []
|
|
964
|
+
const setNames = new Set([
|
|
965
|
+
...SORTED_VARIANT_SET_NAMES,
|
|
966
|
+
...Object.keys(snapshotSets || {})
|
|
967
|
+
])
|
|
968
|
+
|
|
969
|
+
for (const setName of [...setNames].sort()) {
|
|
970
|
+
const comparison = CircuitJsonElementValidator.#compareSets(
|
|
971
|
+
new Set((snapshotSets?.[setName] || []).map(String)),
|
|
972
|
+
new Set(VARIANT_SETS[setName] || [])
|
|
973
|
+
)
|
|
974
|
+
missing.push(
|
|
975
|
+
...comparison.missing.map((value) => ({
|
|
976
|
+
set: setName,
|
|
977
|
+
value
|
|
978
|
+
}))
|
|
979
|
+
)
|
|
980
|
+
unexpected.push(
|
|
981
|
+
...comparison.unexpected.map((value) => ({
|
|
982
|
+
set: setName,
|
|
983
|
+
value
|
|
984
|
+
}))
|
|
985
|
+
)
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return { missing, unexpected }
|
|
989
|
+
}
|
|
773
990
|
}
|