circuitjson-toolkit 1.0.2 → 1.0.10
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/README.md +21 -2
- package/docs/api.md +50 -4
- package/docs/model-format.md +6 -3
- package/package.json +1 -1
- package/spec/library-scope.md +4 -1
- package/src/core/CircuitJsonBomBuilder.mjs +146 -0
- package/src/core/CircuitJsonDocument.mjs +46 -13
- package/src/core/CircuitJsonElementValidator.mjs +773 -0
- package/src/core/CircuitJsonIndexer.mjs +268 -4
- package/src/core/CircuitJsonManufacturingBuilder.mjs +426 -0
- package/src/core/CircuitJsonParser.mjs +22 -6
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +259 -0
- package/src/core/CircuitJsonUnits.mjs +133 -8
- package/src/core/spice/SpiceCompatibilityPreprocessor.mjs +139 -0
- package/src/core/spice/SpiceDirectiveParser.mjs +231 -0
- package/src/core/spice/SpiceFallbackSimulationEngine.mjs +168 -0
- package/src/core/spice/SpiceSimulationDiagnostics.mjs +234 -0
- package/src/core/spice/SpiceSimulationGraphBuilder.mjs +421 -0
- package/src/core/spice/SpiceSimulationGraphSummary.mjs +90 -0
- package/src/core/spice/SpiceSimulationService.mjs +92 -0
- package/src/core/spice/SpiceTimeSeriesNormalizer.mjs +132 -0
- package/src/index.mjs +6 -0
package/README.md
CHANGED
|
@@ -33,10 +33,12 @@ package as a normal npm dependency.
|
|
|
33
33
|
- Build lookup indexes by element type and stable CircuitJSON identifiers
|
|
34
34
|
- Resolve source component and PCB component maps for viewer, QA, export, or
|
|
35
35
|
reporting integrations
|
|
36
|
-
-
|
|
37
|
-
|
|
36
|
+
- Reject unknown element types and invalid core element fields before consumers
|
|
37
|
+
render or query the model
|
|
38
38
|
- Convert CircuitJSON millimeter dimensions and points into mils for render
|
|
39
39
|
adapters that use PCB imperial units internally
|
|
40
|
+
- Run dependency-free SPICE transient examples or normalize injected simulator
|
|
41
|
+
results into complete CircuitJSON simulation experiment element sets
|
|
40
42
|
- Provide root and parser-focused ESM entrypoints
|
|
41
43
|
- Run in browser and Node ESM environments
|
|
42
44
|
- Run entirely with local input data; no network calls are made by the parser
|
|
@@ -106,6 +108,22 @@ const centerMil = CircuitJsonUnits.pointMmToMil(board.center)
|
|
|
106
108
|
const widthMil = CircuitJsonUnits.mmToMil(board.width)
|
|
107
109
|
```
|
|
108
110
|
|
|
111
|
+
Run a local SPICE transient example and receive CircuitJSON experiment output:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { SpiceSimulationService } from 'circuitjson-toolkit'
|
|
115
|
+
|
|
116
|
+
const result = await SpiceSimulationService.simulate(`
|
|
117
|
+
Vmain out 0 DC 3.3
|
|
118
|
+
.PRINT TRAN V(out)
|
|
119
|
+
.tran 1ms 2ms
|
|
120
|
+
.END
|
|
121
|
+
`)
|
|
122
|
+
|
|
123
|
+
console.log(result.simulationCircuitJson)
|
|
124
|
+
console.log(result.graphSummary)
|
|
125
|
+
```
|
|
126
|
+
|
|
109
127
|
Pass CircuitJSON to the 3D renderer as a separate step:
|
|
110
128
|
|
|
111
129
|
```js
|
|
@@ -137,6 +155,7 @@ This package owns reusable CircuitJSON utility behavior only:
|
|
|
137
155
|
- validation and diagnostics for element-array inputs;
|
|
138
156
|
- indexing and lookup maps for common CircuitJSON IDs;
|
|
139
157
|
- small unit conversion helpers for downstream renderer adapters.
|
|
158
|
+
- local SPICE transient graph helpers that return CircuitJSON elements.
|
|
140
159
|
|
|
141
160
|
It does not include native Altium or KiCad parsing, schematic/PCB SVG
|
|
142
161
|
rendering, Three.js rendering, browser UI controls, network fetching, or
|
package/docs/api.md
CHANGED
|
@@ -31,15 +31,21 @@ Parses UTF-8 CircuitJSON bytes from an `ArrayBuffer` or `Uint8Array`.
|
|
|
31
31
|
|
|
32
32
|
### `CircuitJsonDocument.isElement(value)`
|
|
33
33
|
|
|
34
|
-
Returns true when `value` is an object with a non-empty string `type`
|
|
34
|
+
Returns true when `value` is an object with a known non-empty string `type`
|
|
35
|
+
field and passes element-specific validation.
|
|
35
36
|
|
|
36
37
|
### `CircuitJsonDocument.isModel(value)`
|
|
37
38
|
|
|
38
|
-
Returns true when `value` is an array and every item is a CircuitJSON
|
|
39
|
+
Returns true when `value` is an array and every item is a valid CircuitJSON
|
|
40
|
+
element.
|
|
41
|
+
|
|
42
|
+
### `CircuitJsonDocument.validateModel(value)`
|
|
43
|
+
|
|
44
|
+
Returns validation error messages for a candidate CircuitJSON element array.
|
|
39
45
|
|
|
40
46
|
### `CircuitJsonDocument.assertModel(value)`
|
|
41
47
|
|
|
42
|
-
Throws unless `value` is a CircuitJSON element array.
|
|
48
|
+
Throws unless `value` is a valid CircuitJSON element array.
|
|
43
49
|
|
|
44
50
|
### `CircuitJsonDocument.attachMetadata(circuitJson, metadata?)`
|
|
45
51
|
|
|
@@ -74,6 +80,44 @@ the fallback, defaulting to `0`.
|
|
|
74
80
|
|
|
75
81
|
Converts `{ x, y }` millimeter points to `{ x, y }` mil points.
|
|
76
82
|
|
|
83
|
+
## SPICE Simulation
|
|
84
|
+
|
|
85
|
+
### `SpiceCompatibilityPreprocessor.rewrite(spiceString)`
|
|
86
|
+
|
|
87
|
+
Rewrites narrow, supported SPICE compatibility syntax before simulation. The
|
|
88
|
+
current preprocessor handles resistor `TC=` pairs and boolean caret operators
|
|
89
|
+
inside compatible `VALUE` expression blocks.
|
|
90
|
+
|
|
91
|
+
### `SpiceSimulationService.simulate(spiceString)`
|
|
92
|
+
|
|
93
|
+
Runs a local deterministic transient example and returns:
|
|
94
|
+
|
|
95
|
+
- `simulationResultCircuitJson`: CircuitJSON transient voltage/current graph
|
|
96
|
+
elements.
|
|
97
|
+
- `simulationCircuitJson`: a complete CircuitJSON element set containing the
|
|
98
|
+
`simulation_experiment` element with `experiment_type:
|
|
99
|
+
'spice_transient_analysis'` followed by its graph elements.
|
|
100
|
+
- `graphSummary`: a deterministic summary of graph ids, names, point counts,
|
|
101
|
+
time bounds, and min/max values for renderer tests and UI previews.
|
|
102
|
+
- `diagnostics`: non-fatal simulation diagnostics.
|
|
103
|
+
|
|
104
|
+
The service also accepts an injected engine through `new
|
|
105
|
+
SpiceSimulationService({ engine })`. The engine must provide a `simulate`
|
|
106
|
+
method that accepts a preprocessed SPICE netlist string and returns real-valued
|
|
107
|
+
rows with `time`, `voltage`, and `current` data. Returned rows are resampled to
|
|
108
|
+
the `.tran` time grid when transient step and stop parameters are available.
|
|
109
|
+
Probe metadata comments may use `circuitjson_voltage_probe`,
|
|
110
|
+
`simulation_voltage_probe`, `circuitjson_current_probe`, or
|
|
111
|
+
`simulation_current_probe` markers to preserve graph ids, names, source nodes,
|
|
112
|
+
and source trace/component references.
|
|
113
|
+
External `.lib` and `.include` directives, PWL REPEAT source syntax, selected
|
|
114
|
+
PSPICE compatibility patterns, and requested `.PRINT TRAN` vectors that cannot
|
|
115
|
+
be matched to simulator output are reported as warnings for callers that need a
|
|
116
|
+
full simulator path.
|
|
117
|
+
Malformed probe metadata comments are also reported as warnings. Invalid JSON
|
|
118
|
+
uses `spice_probe_metadata_invalid_json`; parsed comments missing required
|
|
119
|
+
string fields use `spice_probe_metadata_invalid_shape`.
|
|
120
|
+
|
|
77
121
|
## Entrypoints
|
|
78
122
|
|
|
79
123
|
The root entrypoint exports all utilities:
|
|
@@ -83,7 +127,9 @@ import {
|
|
|
83
127
|
CircuitJsonDocument,
|
|
84
128
|
CircuitJsonIndexer,
|
|
85
129
|
CircuitJsonParser,
|
|
86
|
-
CircuitJsonUnits
|
|
130
|
+
CircuitJsonUnits,
|
|
131
|
+
SpiceCompatibilityPreprocessor,
|
|
132
|
+
SpiceSimulationService
|
|
87
133
|
} from 'circuitjson-toolkit'
|
|
88
134
|
```
|
|
89
135
|
|
package/docs/model-format.md
CHANGED
|
@@ -11,6 +11,7 @@ The expected top-level value is an array:
|
|
|
11
11
|
{
|
|
12
12
|
"type": "pcb_board",
|
|
13
13
|
"pcb_board_id": "board_1",
|
|
14
|
+
"center": { "x": 0, "y": 0 },
|
|
14
15
|
"width": 50,
|
|
15
16
|
"height": 30,
|
|
16
17
|
"thickness": 1.6
|
|
@@ -18,9 +19,11 @@ The expected top-level value is an array:
|
|
|
18
19
|
]
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
Every element must be an object with a non-empty string `type` field
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
Every element must be an object with a known non-empty string `type` field and
|
|
23
|
+
the required fields for that element. The validator also checks the standard
|
|
24
|
+
identifier convention for known element types, plus strict core fields for
|
|
25
|
+
boards, source components, source ports, schematic components, PCB components,
|
|
26
|
+
and SMT pads.
|
|
24
27
|
|
|
25
28
|
## Metadata
|
|
26
29
|
|
package/package.json
CHANGED
package/spec/library-scope.md
CHANGED
|
@@ -11,6 +11,9 @@ CircuitJSON element arrays.
|
|
|
11
11
|
- Lookup indexes by element type and stable IDs
|
|
12
12
|
- Source and PCB component lookup maps
|
|
13
13
|
- Millimeter-to-mil conversion helpers
|
|
14
|
+
- Local SPICE transient graph helpers that produce complete CircuitJSON
|
|
15
|
+
simulation experiment element sets
|
|
16
|
+
- Deterministic SPICE graph summaries and non-fatal local syntax diagnostics
|
|
14
17
|
- Small documentation and tests for toolkit behavior
|
|
15
18
|
|
|
16
19
|
## Excluded
|
|
@@ -18,7 +21,7 @@ CircuitJSON element arrays.
|
|
|
18
21
|
- Three.js or browser DOM rendering
|
|
19
22
|
- PCB 3D runtime behavior
|
|
20
23
|
- ECAD source parser logic
|
|
21
|
-
-
|
|
24
|
+
- Native ECAD source-format compatibility adapters
|
|
22
25
|
- Network fetching or remote asset loading
|
|
23
26
|
|
|
24
27
|
Renderer fixes belong in renderer packages. Source-format fixes belong in the
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds app-compatible BOM rows from source component elements.
|
|
3
|
+
*/
|
|
4
|
+
export class CircuitJsonBomBuilder {
|
|
5
|
+
/**
|
|
6
|
+
* Builds grouped BOM rows from a CircuitJSON element array.
|
|
7
|
+
* @param {object[]} circuitJson CircuitJSON model.
|
|
8
|
+
* @returns {object[]}
|
|
9
|
+
*/
|
|
10
|
+
static build(circuitJson) {
|
|
11
|
+
const groups = new Map()
|
|
12
|
+
for (const component of circuitJson || []) {
|
|
13
|
+
if (component?.type !== 'source_component') continue
|
|
14
|
+
const designator = CircuitJsonBomBuilder.#designator(component)
|
|
15
|
+
if (!designator) continue
|
|
16
|
+
|
|
17
|
+
const key = CircuitJsonBomBuilder.#groupKey(component)
|
|
18
|
+
if (!groups.has(key)) {
|
|
19
|
+
groups.set(key, {
|
|
20
|
+
designators: [],
|
|
21
|
+
quantity: 0,
|
|
22
|
+
value: CircuitJsonBomBuilder.#value(component),
|
|
23
|
+
pattern: CircuitJsonBomBuilder.#pattern(component),
|
|
24
|
+
source: CircuitJsonBomBuilder.#source(component),
|
|
25
|
+
supplierPartNumber:
|
|
26
|
+
CircuitJsonBomBuilder.#supplierPartNumber(component)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
const row = groups.get(key)
|
|
30
|
+
row.designators.push(designator)
|
|
31
|
+
row.quantity = row.designators.length
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [...groups.values()].map((row) => ({
|
|
35
|
+
...row,
|
|
36
|
+
designators: row.designators.sort(
|
|
37
|
+
CircuitJsonBomBuilder.#compareDesignators
|
|
38
|
+
)
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the display designator for one source component.
|
|
44
|
+
* @param {object} component Source component.
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
static #designator(component) {
|
|
48
|
+
return String(
|
|
49
|
+
component.name ||
|
|
50
|
+
component.reference ||
|
|
51
|
+
component.designator ||
|
|
52
|
+
component.source_component_id ||
|
|
53
|
+
''
|
|
54
|
+
).trim()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Builds a stable grouping key from BOM-facing fields.
|
|
59
|
+
* @param {object} component Source component.
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
static #groupKey(component) {
|
|
63
|
+
return [
|
|
64
|
+
CircuitJsonBomBuilder.#value(component),
|
|
65
|
+
CircuitJsonBomBuilder.#pattern(component),
|
|
66
|
+
CircuitJsonBomBuilder.#source(component),
|
|
67
|
+
CircuitJsonBomBuilder.#supplierPartNumber(component)
|
|
68
|
+
].join('\u001f')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolves a BOM value from common source component fields.
|
|
73
|
+
* @param {object} component Source component.
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
static #value(component) {
|
|
77
|
+
return String(
|
|
78
|
+
component.value ??
|
|
79
|
+
component.resistance ??
|
|
80
|
+
component.capacitance ??
|
|
81
|
+
component.inductance ??
|
|
82
|
+
component.frequency ??
|
|
83
|
+
component.voltage ??
|
|
84
|
+
''
|
|
85
|
+
).trim()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolves a footprint/package pattern label.
|
|
90
|
+
* @param {object} component Source component.
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
static #pattern(component) {
|
|
94
|
+
return String(
|
|
95
|
+
component.footprint ??
|
|
96
|
+
component.package ??
|
|
97
|
+
component.package_name ??
|
|
98
|
+
component.ftype ??
|
|
99
|
+
''
|
|
100
|
+
).trim()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolves manufacturer or catalog source text.
|
|
105
|
+
* @param {object} component Source component.
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
static #source(component) {
|
|
109
|
+
return String(
|
|
110
|
+
component.manufacturer_part_number ??
|
|
111
|
+
component.mpn ??
|
|
112
|
+
component.part_number ??
|
|
113
|
+
component.supplier_part_number ??
|
|
114
|
+
''
|
|
115
|
+
).trim()
|
|
116
|
+
}
|
|
117
|
+
|
|
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
|
+
/**
|
|
135
|
+
* Compares designators using natural ordering.
|
|
136
|
+
* @param {string} left Left designator.
|
|
137
|
+
* @param {string} right Right designator.
|
|
138
|
+
* @returns {number}
|
|
139
|
+
*/
|
|
140
|
+
static #compareDesignators(left, right) {
|
|
141
|
+
return String(left).localeCompare(String(right), undefined, {
|
|
142
|
+
numeric: true,
|
|
143
|
+
sensitivity: 'base'
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CircuitJsonElementValidator } from './CircuitJsonElementValidator.mjs'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Validates serialized CircuitJSON element arrays.
|
|
3
5
|
*/
|
|
@@ -8,12 +10,7 @@ export class CircuitJsonDocument {
|
|
|
8
10
|
* @returns {boolean}
|
|
9
11
|
*/
|
|
10
12
|
static isElement(value) {
|
|
11
|
-
return (
|
|
12
|
-
Boolean(value) &&
|
|
13
|
-
typeof value === 'object' &&
|
|
14
|
-
typeof value.type === 'string' &&
|
|
15
|
-
value.type.trim().length > 0
|
|
16
|
-
)
|
|
13
|
+
return CircuitJsonElementValidator.validateElement(value).length === 0
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
/**
|
|
@@ -22,10 +19,16 @@ export class CircuitJsonDocument {
|
|
|
22
19
|
* @returns {boolean}
|
|
23
20
|
*/
|
|
24
21
|
static isModel(value) {
|
|
25
|
-
return (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
return CircuitJsonElementValidator.validateModel(value).length === 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns validation errors for a candidate CircuitJSON element array.
|
|
27
|
+
* @param {unknown} value Candidate model.
|
|
28
|
+
* @returns {string[]}
|
|
29
|
+
*/
|
|
30
|
+
static validateModel(value) {
|
|
31
|
+
return CircuitJsonElementValidator.validateModel(value)
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
@@ -34,8 +37,9 @@ export class CircuitJsonDocument {
|
|
|
34
37
|
* @returns {void}
|
|
35
38
|
*/
|
|
36
39
|
static assertModel(value) {
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
const errors = CircuitJsonDocument.validateModel(value)
|
|
41
|
+
if (errors.length) {
|
|
42
|
+
throw new TypeError(errors[0])
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -43,7 +47,7 @@ export class CircuitJsonDocument {
|
|
|
43
47
|
* Attaches non-serialized metadata to a CircuitJSON array.
|
|
44
48
|
* @template {object[]} T
|
|
45
49
|
* @param {T} circuitJson CircuitJSON model.
|
|
46
|
-
* @param {{ fileName?: string, fileType?: string, kind?: string }} [metadata]
|
|
50
|
+
* @param {{ fileName?: string, fileType?: string, kind?: string, diagnostics?: object[], bom?: object[], supportMatrix?: object, manufacturing?: object }} [metadata]
|
|
47
51
|
* @returns {T}
|
|
48
52
|
*/
|
|
49
53
|
static attachMetadata(circuitJson, metadata = {}) {
|
|
@@ -72,6 +76,35 @@ export class CircuitJsonDocument {
|
|
|
72
76
|
enumerable: true,
|
|
73
77
|
value: 'circuitjson',
|
|
74
78
|
writable: true
|
|
79
|
+
},
|
|
80
|
+
diagnostics: {
|
|
81
|
+
configurable: true,
|
|
82
|
+
enumerable: true,
|
|
83
|
+
value: Array.isArray(metadata.diagnostics)
|
|
84
|
+
? metadata.diagnostics
|
|
85
|
+
: [],
|
|
86
|
+
writable: true
|
|
87
|
+
},
|
|
88
|
+
bom: {
|
|
89
|
+
configurable: true,
|
|
90
|
+
enumerable: true,
|
|
91
|
+
value: Array.isArray(metadata.bom) ? metadata.bom : [],
|
|
92
|
+
writable: true
|
|
93
|
+
},
|
|
94
|
+
supportMatrix: {
|
|
95
|
+
configurable: true,
|
|
96
|
+
enumerable: true,
|
|
97
|
+
value: metadata.supportMatrix || null,
|
|
98
|
+
writable: true
|
|
99
|
+
},
|
|
100
|
+
manufacturing: {
|
|
101
|
+
configurable: true,
|
|
102
|
+
enumerable: true,
|
|
103
|
+
value: metadata.manufacturing || {
|
|
104
|
+
pickAndPlaceRows: [],
|
|
105
|
+
routingDsn: ''
|
|
106
|
+
},
|
|
107
|
+
writable: true
|
|
75
108
|
}
|
|
76
109
|
})
|
|
77
110
|
|