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 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
- - Preserve unknown element types instead of imposing renderer-specific
37
- semantics
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` field.
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 element.
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
 
@@ -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. The
22
- toolkit intentionally does not require every possible CircuitJSON field because
23
- hosts may use partial files, future element types, or renderer-specific subsets.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuitjson-toolkit",
3
- "version": "1.0.2",
3
+ "version": "1.0.10",
4
4
  "description": "Dependency-free CircuitJSON parsing, validation, indexing, and utility helpers",
5
5
  "keywords": [
6
6
  "circuitjson",
@@ -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
- - Altium, KiCad, or other source-format compatibility adapters
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
- Array.isArray(value) &&
27
- value.every((element) => CircuitJsonDocument.isElement(element))
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
- if (!CircuitJsonDocument.isModel(value)) {
38
- throw new TypeError('Expected a CircuitJSON element array.')
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