circuitjson-toolkit 1.0.10 → 1.0.17
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 +20 -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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
const COMPONENT_ICON_BY_TYPE = {
|
|
2
|
+
battery: 'battery',
|
|
3
|
+
capacitor: 'capacitor',
|
|
4
|
+
chip: 'chip',
|
|
5
|
+
connector: 'connector',
|
|
6
|
+
crystal: 'crystal',
|
|
7
|
+
current_source: 'source',
|
|
8
|
+
diode: 'diode',
|
|
9
|
+
fiducial: 'fiducial',
|
|
10
|
+
fuse: 'fuse',
|
|
11
|
+
ground: 'ground',
|
|
12
|
+
inductor: 'inductor',
|
|
13
|
+
interconnect: 'connector',
|
|
14
|
+
led: 'led',
|
|
15
|
+
mosfet: 'transistor',
|
|
16
|
+
op_amp: 'op-amp',
|
|
17
|
+
pin_header: 'connector',
|
|
18
|
+
potentiometer: 'potentiometer',
|
|
19
|
+
push_button: 'switch',
|
|
20
|
+
resistor: 'resistor',
|
|
21
|
+
resonator: 'crystal',
|
|
22
|
+
switch: 'switch',
|
|
23
|
+
test_point: 'test-point',
|
|
24
|
+
transistor: 'transistor',
|
|
25
|
+
voltage_probe: 'probe',
|
|
26
|
+
voltage_source: 'source'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes generic source-level CircuitJSON metadata for stable consumers.
|
|
31
|
+
*/
|
|
32
|
+
export class CircuitJsonSourceMetadata {
|
|
33
|
+
/**
|
|
34
|
+
* Builds a safe source net name from display-oriented text.
|
|
35
|
+
* @param {unknown} value Source net label.
|
|
36
|
+
* @param {{ fallback?: string, usedNames?: Set<string> }} [options]
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
static normalizeSourceNetName(value, options = {}) {
|
|
40
|
+
const fallback = String(options.fallback || 'net').trim() || 'net'
|
|
41
|
+
let name = String(value || '').trim() || fallback
|
|
42
|
+
name = name
|
|
43
|
+
.replaceAll('+', '_P')
|
|
44
|
+
.replaceAll('-', '_')
|
|
45
|
+
.replace(/[^A-Za-z0-9_]+/gu, '_')
|
|
46
|
+
.replace(/_+/gu, '_')
|
|
47
|
+
.replace(/_+$/u, '')
|
|
48
|
+
if (!name) {
|
|
49
|
+
name = fallback.replace(/[^A-Za-z0-9_]+/gu, '_') || 'net'
|
|
50
|
+
}
|
|
51
|
+
if (/^[0-9]/u.test(name)) {
|
|
52
|
+
name = 'net_' + name
|
|
53
|
+
}
|
|
54
|
+
return CircuitJsonSourceMetadata.#dedupeName(name, options.usedNames)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Builds a stable source port name from a pad or pin label.
|
|
59
|
+
* @param {unknown} value Source port label.
|
|
60
|
+
* @param {{ fallback?: string, usedNames?: Set<string> }} [options]
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
static normalizeSourcePortName(value, options = {}) {
|
|
64
|
+
const text = String(value ?? '').trim()
|
|
65
|
+
const fallback = String(options.fallback || 'pin').trim() || 'pin'
|
|
66
|
+
const name = /^[0-9]+$/u.test(text) ? 'pin' + Number(text) : text
|
|
67
|
+
return CircuitJsonSourceMetadata.#dedupeName(
|
|
68
|
+
name || fallback,
|
|
69
|
+
options.usedNames
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Builds stable semantic metadata for one source component.
|
|
75
|
+
* @param {object} component Source component row.
|
|
76
|
+
* @returns {{ sourceFtype: string, componentType: string, componentIcon: string, supplierPartNumber: string, supplierPartNumbers: Record<string, string> }}
|
|
77
|
+
*/
|
|
78
|
+
static normalizeSourceComponent(component) {
|
|
79
|
+
const sourceFtype =
|
|
80
|
+
CircuitJsonSourceMetadata.#sourceFtype(component) ||
|
|
81
|
+
CircuitJsonSourceMetadata.#inferSourceFtype(component)
|
|
82
|
+
const componentType = sourceFtype
|
|
83
|
+
.replace(/^simple_/u, '')
|
|
84
|
+
.replaceAll('_', '-')
|
|
85
|
+
const supplierPartNumbers =
|
|
86
|
+
CircuitJsonSourceMetadata.#supplierPartNumbers(component)
|
|
87
|
+
return {
|
|
88
|
+
sourceFtype,
|
|
89
|
+
componentType,
|
|
90
|
+
componentIcon:
|
|
91
|
+
COMPONENT_ICON_BY_TYPE[componentType.replaceAll('-', '_')] ||
|
|
92
|
+
componentType ||
|
|
93
|
+
'component',
|
|
94
|
+
supplierPartNumber: String(
|
|
95
|
+
Object.values(supplierPartNumbers).find(Boolean) || ''
|
|
96
|
+
).trim(),
|
|
97
|
+
supplierPartNumbers
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Returns a unique name against an optional used-name set.
|
|
103
|
+
* @param {string} name Base name.
|
|
104
|
+
* @param {Set<string> | undefined} usedNames Existing names.
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
static #dedupeName(name, usedNames) {
|
|
108
|
+
if (!(usedNames instanceof Set) || !usedNames.has(name)) return name
|
|
109
|
+
let index = 2
|
|
110
|
+
let candidate = name + '_' + index
|
|
111
|
+
while (usedNames.has(candidate)) {
|
|
112
|
+
index += 1
|
|
113
|
+
candidate = name + '_' + index
|
|
114
|
+
}
|
|
115
|
+
return candidate
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolves an explicit source component function type.
|
|
120
|
+
* @param {object} component Source component row.
|
|
121
|
+
* @returns {string}
|
|
122
|
+
*/
|
|
123
|
+
static #sourceFtype(component) {
|
|
124
|
+
return String(component?.ftype || component?.sourceFtype || '').trim()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Infers a source component function type from generic fields.
|
|
129
|
+
* @param {object} component Source component row.
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
static #inferSourceFtype(component) {
|
|
133
|
+
const reference = String(
|
|
134
|
+
component?.name ||
|
|
135
|
+
component?.reference ||
|
|
136
|
+
component?.designator ||
|
|
137
|
+
''
|
|
138
|
+
).trim()
|
|
139
|
+
const text = [
|
|
140
|
+
reference,
|
|
141
|
+
component?.footprint,
|
|
142
|
+
component?.package,
|
|
143
|
+
component?.package_name,
|
|
144
|
+
component?.value,
|
|
145
|
+
component?.description
|
|
146
|
+
]
|
|
147
|
+
.map((value) => String(value || '').toLowerCase())
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.join(' ')
|
|
150
|
+
|
|
151
|
+
if (text.includes('led')) return 'simple_led'
|
|
152
|
+
if (/^tp[0-9A-Z_-]*/iu.test(reference) || text.includes('test point')) {
|
|
153
|
+
return 'simple_test_point'
|
|
154
|
+
}
|
|
155
|
+
if (/^fid[0-9A-Z_-]*/iu.test(reference) || text.includes('fiducial')) {
|
|
156
|
+
return 'simple_fiducial'
|
|
157
|
+
}
|
|
158
|
+
if (/^sw[0-9A-Z_-]*/iu.test(reference) || text.includes('switch')) {
|
|
159
|
+
return 'simple_switch'
|
|
160
|
+
}
|
|
161
|
+
if (/^r[0-9A-Z_-]*/iu.test(reference) || component?.resistance) {
|
|
162
|
+
return 'simple_resistor'
|
|
163
|
+
}
|
|
164
|
+
if (/^c[0-9A-Z_-]*/iu.test(reference) || component?.capacitance) {
|
|
165
|
+
return 'simple_capacitor'
|
|
166
|
+
}
|
|
167
|
+
if (/^l[0-9A-Z_-]*/iu.test(reference) || component?.inductance) {
|
|
168
|
+
return 'simple_inductor'
|
|
169
|
+
}
|
|
170
|
+
if (text.includes('mosfet')) return 'simple_mosfet'
|
|
171
|
+
if (/^q[0-9A-Z_-]*/iu.test(reference) || text.includes('transistor')) {
|
|
172
|
+
return 'simple_transistor'
|
|
173
|
+
}
|
|
174
|
+
if (/^d[0-9A-Z_-]*/iu.test(reference)) return 'simple_diode'
|
|
175
|
+
if (/^(u|ic)[0-9A-Z_-]*/iu.test(reference) || text.includes('chip')) {
|
|
176
|
+
return 'simple_chip'
|
|
177
|
+
}
|
|
178
|
+
return ''
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Normalizes supplier part fields into a stable keyed object.
|
|
183
|
+
* @param {object} component Source component row.
|
|
184
|
+
* @returns {Record<string, string>}
|
|
185
|
+
*/
|
|
186
|
+
static #supplierPartNumbers(component) {
|
|
187
|
+
const numbers = {}
|
|
188
|
+
for (const field of ['supplier_part_numbers', 'supplierPartNumbers']) {
|
|
189
|
+
const value = component?.[field]
|
|
190
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
194
|
+
const normalized = String(entry || '').trim()
|
|
195
|
+
if (normalized) numbers[String(key || 'supplier')] = normalized
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
CircuitJsonSourceMetadata.#addSupplierNumber(
|
|
199
|
+
numbers,
|
|
200
|
+
'supplier',
|
|
201
|
+
component?.supplier_part_number ?? component?.supplierPartNumber
|
|
202
|
+
)
|
|
203
|
+
CircuitJsonSourceMetadata.#addSupplierNumber(
|
|
204
|
+
numbers,
|
|
205
|
+
'distributor',
|
|
206
|
+
component?.distributor_part_number ??
|
|
207
|
+
component?.distributorPartNumber
|
|
208
|
+
)
|
|
209
|
+
CircuitJsonSourceMetadata.#addSupplierNumber(
|
|
210
|
+
numbers,
|
|
211
|
+
'assembly',
|
|
212
|
+
component?.assembly_part_number ?? component?.assemblyPartNumber
|
|
213
|
+
)
|
|
214
|
+
CircuitJsonSourceMetadata.#addSupplierNumber(
|
|
215
|
+
numbers,
|
|
216
|
+
'catalog',
|
|
217
|
+
component?.catalog_part_number ?? component?.catalogPartNumber
|
|
218
|
+
)
|
|
219
|
+
return numbers
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Adds one supplier part number when present.
|
|
224
|
+
* @param {Record<string, string>} numbers Supplier number map.
|
|
225
|
+
* @param {string} key Supplier key.
|
|
226
|
+
* @param {unknown} value Supplier part value.
|
|
227
|
+
* @returns {void}
|
|
228
|
+
*/
|
|
229
|
+
static #addSupplierNumber(numbers, key, value) {
|
|
230
|
+
const normalized = String(value || '').trim()
|
|
231
|
+
if (normalized && !numbers[key]) numbers[key] = normalized
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -8,7 +8,9 @@ const PCB_RENDERED_TYPES = new Set([
|
|
|
8
8
|
'pcb_copper_text',
|
|
9
9
|
'pcb_courtyard',
|
|
10
10
|
'pcb_courtyard_circle',
|
|
11
|
+
'pcb_courtyard_line',
|
|
11
12
|
'pcb_courtyard_outline',
|
|
13
|
+
'pcb_courtyard_path',
|
|
12
14
|
'pcb_courtyard_pill',
|
|
13
15
|
'pcb_courtyard_polygon',
|
|
14
16
|
'pcb_courtyard_rect',
|
|
@@ -78,6 +80,81 @@ const ROUTING_DSN_TYPES = new Set([
|
|
|
78
80
|
'source_net'
|
|
79
81
|
])
|
|
80
82
|
|
|
83
|
+
const VARIANT_ROWS_BY_SET = {
|
|
84
|
+
sourceComponentFtypes: {
|
|
85
|
+
type: 'source_component',
|
|
86
|
+
group: 'ftype',
|
|
87
|
+
capability: 'bom',
|
|
88
|
+
status: 'grouped'
|
|
89
|
+
},
|
|
90
|
+
pcbBoardShapes: {
|
|
91
|
+
type: 'pcb_board',
|
|
92
|
+
group: 'shape',
|
|
93
|
+
capability: 'pcb',
|
|
94
|
+
status: 'rendered'
|
|
95
|
+
},
|
|
96
|
+
pcbSmtPadShapes: {
|
|
97
|
+
type: 'pcb_smtpad',
|
|
98
|
+
group: 'shape',
|
|
99
|
+
capability: 'pcb',
|
|
100
|
+
status: 'rendered'
|
|
101
|
+
},
|
|
102
|
+
pcbHoleShapes: {
|
|
103
|
+
type: 'pcb_hole',
|
|
104
|
+
group: 'hole_shape',
|
|
105
|
+
capability: 'pcb',
|
|
106
|
+
status: 'rendered'
|
|
107
|
+
},
|
|
108
|
+
pcbPlatedHoleShapes: {
|
|
109
|
+
type: 'pcb_plated_hole',
|
|
110
|
+
group: 'shape',
|
|
111
|
+
capability: 'pcb',
|
|
112
|
+
status: 'rendered'
|
|
113
|
+
},
|
|
114
|
+
pcbPlatedHoleHoleShapes: {
|
|
115
|
+
type: 'pcb_plated_hole',
|
|
116
|
+
group: 'hole_shape',
|
|
117
|
+
capability: 'pcb',
|
|
118
|
+
status: 'rendered'
|
|
119
|
+
},
|
|
120
|
+
pcbSolderPasteShapes: {
|
|
121
|
+
type: 'pcb_solder_paste',
|
|
122
|
+
group: 'shape',
|
|
123
|
+
capability: 'pcb',
|
|
124
|
+
status: 'rendered'
|
|
125
|
+
},
|
|
126
|
+
pcbCutoutShapes: {
|
|
127
|
+
type: 'pcb_cutout',
|
|
128
|
+
group: 'shape',
|
|
129
|
+
capability: 'pcb',
|
|
130
|
+
status: 'rendered'
|
|
131
|
+
},
|
|
132
|
+
pcbCopperPourShapes: {
|
|
133
|
+
type: 'pcb_copper_pour',
|
|
134
|
+
group: 'shape',
|
|
135
|
+
capability: 'pcb',
|
|
136
|
+
status: 'rendered'
|
|
137
|
+
},
|
|
138
|
+
simulationSourceKinds: {
|
|
139
|
+
type: 'simulation_voltage_source',
|
|
140
|
+
group: 'kind',
|
|
141
|
+
capability: 'simulation',
|
|
142
|
+
status: 'summarized'
|
|
143
|
+
},
|
|
144
|
+
simulationWaveShapes: {
|
|
145
|
+
type: 'simulation_voltage_source',
|
|
146
|
+
group: 'wave_shape',
|
|
147
|
+
capability: 'simulation',
|
|
148
|
+
status: 'summarized'
|
|
149
|
+
},
|
|
150
|
+
simulationExperimentMethods: {
|
|
151
|
+
type: 'simulation_experiment',
|
|
152
|
+
group: 'spice_options.method',
|
|
153
|
+
capability: 'simulation',
|
|
154
|
+
status: 'summarized'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
81
158
|
/**
|
|
82
159
|
* Builds document-level support coverage reports from known element metadata.
|
|
83
160
|
*/
|
|
@@ -85,7 +162,7 @@ export class CircuitJsonSupportMatrixBuilder {
|
|
|
85
162
|
/**
|
|
86
163
|
* Builds a support matrix for the known schema snapshot and present rows.
|
|
87
164
|
* @param {object[]} [circuitJson] Parsed element array.
|
|
88
|
-
* @returns {{ sourceFormat: string, totals: object, rows: object[], gaps: object[] }}
|
|
165
|
+
* @returns {{ sourceFormat: string, totals: object, rows: object[], variantRows: object[], gaps: object[] }}
|
|
89
166
|
*/
|
|
90
167
|
static build(circuitJson = []) {
|
|
91
168
|
const elements = Array.isArray(circuitJson) ? circuitJson : []
|
|
@@ -101,11 +178,18 @@ export class CircuitJsonSupportMatrixBuilder {
|
|
|
101
178
|
presentTypes.has(type)
|
|
102
179
|
)
|
|
103
180
|
)
|
|
181
|
+
const variantRows =
|
|
182
|
+
CircuitJsonSupportMatrixBuilder.#variantRows(elements)
|
|
104
183
|
|
|
105
184
|
return {
|
|
106
185
|
sourceFormat: 'circuitjson',
|
|
107
|
-
totals: CircuitJsonSupportMatrixBuilder.#totals(
|
|
186
|
+
totals: CircuitJsonSupportMatrixBuilder.#totals(
|
|
187
|
+
rows,
|
|
188
|
+
presentTypes,
|
|
189
|
+
variantRows
|
|
190
|
+
),
|
|
108
191
|
rows,
|
|
192
|
+
variantRows,
|
|
109
193
|
gaps: rows.flatMap((row) =>
|
|
110
194
|
CircuitJsonSupportMatrixBuilder.#gaps(row)
|
|
111
195
|
)
|
|
@@ -142,6 +226,7 @@ export class CircuitJsonSupportMatrixBuilder {
|
|
|
142
226
|
diagnostics: CircuitJsonSupportMatrixBuilder.#diagnostics(type),
|
|
143
227
|
schematic: SCHEMATIC_RENDERED_TYPES.has(type) ? 'rendered' : 'none',
|
|
144
228
|
pcb: PCB_RENDERED_TYPES.has(type) ? 'rendered' : 'none',
|
|
229
|
+
scene3d: type === 'cad_component' ? 'external-model' : 'none',
|
|
145
230
|
bom: type === 'source_component' ? 'grouped' : 'none',
|
|
146
231
|
manufacturing: CircuitJsonSupportMatrixBuilder.#manufacturing(type),
|
|
147
232
|
simulation: type.startsWith('simulation_') ? 'preserved' : 'none'
|
|
@@ -182,6 +267,7 @@ export class CircuitJsonSupportMatrixBuilder {
|
|
|
182
267
|
if (
|
|
183
268
|
capabilities.pcb === 'rendered' ||
|
|
184
269
|
capabilities.schematic === 'rendered' ||
|
|
270
|
+
capabilities.scene3d === 'external-model' ||
|
|
185
271
|
capabilities.diagnostics === 'normalized'
|
|
186
272
|
) {
|
|
187
273
|
return capabilities.manufacturing === 'routing-dsn'
|
|
@@ -222,13 +308,145 @@ export class CircuitJsonSupportMatrixBuilder {
|
|
|
222
308
|
return []
|
|
223
309
|
}
|
|
224
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Builds variant coverage rows from active schema metadata.
|
|
313
|
+
* @param {object[]} elements Parsed element rows.
|
|
314
|
+
* @returns {object[]}
|
|
315
|
+
*/
|
|
316
|
+
static #variantRows(elements) {
|
|
317
|
+
const schema = CircuitJsonElementValidator.variantSets()
|
|
318
|
+
const present =
|
|
319
|
+
CircuitJsonSupportMatrixBuilder.#presentVariants(elements)
|
|
320
|
+
return Object.entries(schema).flatMap(([setName, values]) => {
|
|
321
|
+
const definition = VARIANT_ROWS_BY_SET[setName]
|
|
322
|
+
if (!definition) return []
|
|
323
|
+
return values.map((value) =>
|
|
324
|
+
CircuitJsonSupportMatrixBuilder.#variantRow(
|
|
325
|
+
setName,
|
|
326
|
+
definition,
|
|
327
|
+
value,
|
|
328
|
+
present
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Builds one variant coverage row.
|
|
336
|
+
* @param {string} setName Variant set name.
|
|
337
|
+
* @param {object} definition Row definition.
|
|
338
|
+
* @param {string} value Variant value.
|
|
339
|
+
* @param {Map<string, Set<string>>} present Present variant values.
|
|
340
|
+
* @returns {object}
|
|
341
|
+
*/
|
|
342
|
+
static #variantRow(setName, definition, value, present) {
|
|
343
|
+
const isPresent = Boolean(present.get(setName)?.has(value))
|
|
344
|
+
return {
|
|
345
|
+
set: setName,
|
|
346
|
+
type: definition.type,
|
|
347
|
+
group: definition.group,
|
|
348
|
+
value,
|
|
349
|
+
present: isPresent,
|
|
350
|
+
capability: definition.capability,
|
|
351
|
+
status: definition.status,
|
|
352
|
+
note: CircuitJsonSupportMatrixBuilder.#variantNote(
|
|
353
|
+
definition,
|
|
354
|
+
value,
|
|
355
|
+
isPresent
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Builds a short human-readable variant support note.
|
|
362
|
+
* @param {object} definition Variant row definition.
|
|
363
|
+
* @param {string} value Variant value.
|
|
364
|
+
* @param {boolean} present Whether the variant is present.
|
|
365
|
+
* @returns {string}
|
|
366
|
+
*/
|
|
367
|
+
static #variantNote(definition, value, present) {
|
|
368
|
+
const label =
|
|
369
|
+
definition.type + '.' + definition.group + ' ' + value + ' is '
|
|
370
|
+
if (!present) return label + 'known but not present.'
|
|
371
|
+
if (definition.status === 'rendered') return label + 'rendered.'
|
|
372
|
+
if (definition.status === 'grouped') {
|
|
373
|
+
return label + 'used for BOM grouping.'
|
|
374
|
+
}
|
|
375
|
+
if (definition.status === 'summarized') {
|
|
376
|
+
return label + 'summarized in simulation setup.'
|
|
377
|
+
}
|
|
378
|
+
return label + 'preserved as metadata.'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Extracts variant values present in the current document.
|
|
383
|
+
* @param {object[]} elements Parsed element rows.
|
|
384
|
+
* @returns {Map<string, Set<string>>}
|
|
385
|
+
*/
|
|
386
|
+
static #presentVariants(elements) {
|
|
387
|
+
const present = new Map()
|
|
388
|
+
const add = (setName, value) => {
|
|
389
|
+
const text = String(value || '').trim()
|
|
390
|
+
if (!text) return
|
|
391
|
+
if (!present.has(setName)) present.set(setName, new Set())
|
|
392
|
+
present.get(setName).add(text)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const element of elements) {
|
|
396
|
+
if (element?.type === 'source_component') {
|
|
397
|
+
add('sourceComponentFtypes', element.ftype)
|
|
398
|
+
}
|
|
399
|
+
if (element?.type === 'pcb_board') {
|
|
400
|
+
add('pcbBoardShapes', element.shape || 'rect')
|
|
401
|
+
}
|
|
402
|
+
if (element?.type === 'pcb_smtpad') {
|
|
403
|
+
add('pcbSmtPadShapes', element.shape)
|
|
404
|
+
}
|
|
405
|
+
if (element?.type === 'pcb_hole') {
|
|
406
|
+
add('pcbHoleShapes', element.hole_shape || element.shape)
|
|
407
|
+
}
|
|
408
|
+
if (element?.type === 'pcb_plated_hole') {
|
|
409
|
+
add('pcbPlatedHoleShapes', element.shape || 'circle')
|
|
410
|
+
add('pcbPlatedHoleHoleShapes', element.hole_shape)
|
|
411
|
+
}
|
|
412
|
+
if (element?.type === 'pcb_solder_paste') {
|
|
413
|
+
add('pcbSolderPasteShapes', element.shape)
|
|
414
|
+
}
|
|
415
|
+
if (element?.type === 'pcb_cutout') {
|
|
416
|
+
add('pcbCutoutShapes', element.shape || 'rect')
|
|
417
|
+
}
|
|
418
|
+
if (element?.type === 'pcb_copper_pour') {
|
|
419
|
+
add('pcbCopperPourShapes', element.shape || 'polygon')
|
|
420
|
+
}
|
|
421
|
+
if (
|
|
422
|
+
element?.type === 'simulation_voltage_source' ||
|
|
423
|
+
element?.type === 'simulation_current_source'
|
|
424
|
+
) {
|
|
425
|
+
add(
|
|
426
|
+
'simulationSourceKinds',
|
|
427
|
+
element.source_type || element.sourceType || element.kind
|
|
428
|
+
)
|
|
429
|
+
add('simulationWaveShapes', element.wave_shape)
|
|
430
|
+
}
|
|
431
|
+
if (element?.type === 'simulation_experiment') {
|
|
432
|
+
add(
|
|
433
|
+
'simulationExperimentMethods',
|
|
434
|
+
element.spice_options?.method
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return present
|
|
440
|
+
}
|
|
441
|
+
|
|
225
442
|
/**
|
|
226
443
|
* Builds aggregate matrix counts.
|
|
227
444
|
* @param {object[]} rows Matrix rows.
|
|
228
445
|
* @param {Set<string>} presentTypes Present element types.
|
|
446
|
+
* @param {object[]} variantRows Variant coverage rows.
|
|
229
447
|
* @returns {object}
|
|
230
448
|
*/
|
|
231
|
-
static #totals(rows, presentTypes) {
|
|
449
|
+
static #totals(rows, presentTypes, variantRows) {
|
|
232
450
|
return {
|
|
233
451
|
knownElementTypes: rows.length,
|
|
234
452
|
presentElementTypes: rows.filter((row) => row.present).length,
|
|
@@ -236,7 +454,8 @@ export class CircuitJsonSupportMatrixBuilder {
|
|
|
236
454
|
(row) =>
|
|
237
455
|
row.present &&
|
|
238
456
|
(row.capabilities.pcb === 'rendered' ||
|
|
239
|
-
row.capabilities.schematic === 'rendered'
|
|
457
|
+
row.capabilities.schematic === 'rendered' ||
|
|
458
|
+
row.capabilities.scene3d === 'external-model')
|
|
240
459
|
).length,
|
|
241
460
|
diagnosticElementTypes: rows.filter(
|
|
242
461
|
(row) =>
|
|
@@ -244,7 +463,10 @@ export class CircuitJsonSupportMatrixBuilder {
|
|
|
244
463
|
).length,
|
|
245
464
|
unknownPresentElementTypes: [...presentTypes].filter(
|
|
246
465
|
(type) => !rows.some((row) => row.type === type)
|
|
247
|
-
).length
|
|
466
|
+
).length,
|
|
467
|
+
knownVariantValues: variantRows.length,
|
|
468
|
+
presentVariantValues: variantRows.filter((row) => row.present)
|
|
469
|
+
.length
|
|
248
470
|
}
|
|
249
471
|
}
|
|
250
472
|
|