circuitjson-toolkit 1.0.3 → 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.
Files changed (54) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +21 -2
  3. package/docs/api.md +50 -4
  4. package/docs/model-format.md +21 -3
  5. package/package.json +3 -2
  6. package/spec/library-scope.md +4 -1
  7. package/src/core/CircuitJsonBomBuilder.mjs +143 -0
  8. package/src/core/CircuitJsonDocument.mjs +46 -13
  9. package/src/core/CircuitJsonElementValidator.mjs +990 -0
  10. package/src/core/CircuitJsonIndexer.mjs +773 -4
  11. package/src/core/CircuitJsonManufacturingBuilder.mjs +898 -0
  12. package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
  13. package/src/core/CircuitJsonParser.mjs +22 -6
  14. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
  15. package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
  16. package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
  17. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
  18. package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
  19. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
  20. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
  21. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
  22. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
  23. package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
  24. package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
  25. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
  26. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
  27. package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
  28. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
  29. package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
  30. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +481 -0
  31. package/src/core/CircuitJsonUnits.mjs +133 -8
  32. package/src/core/PcbBoundsSelectionModel.mjs +250 -0
  33. package/src/core/PcbCandidateSelectionModel.mjs +77 -0
  34. package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
  35. package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
  36. package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
  37. package/src/core/spice/SpiceCompatibilityPreprocessor.mjs +139 -0
  38. package/src/core/spice/SpiceDirectiveParser.mjs +231 -0
  39. package/src/core/spice/SpiceFallbackSimulationEngine.mjs +168 -0
  40. package/src/core/spice/SpiceSimulationDiagnostics.mjs +234 -0
  41. package/src/core/spice/SpiceSimulationGraphBuilder.mjs +421 -0
  42. package/src/core/spice/SpiceSimulationGraphSummary.mjs +90 -0
  43. package/src/core/spice/SpiceSimulationService.mjs +92 -0
  44. package/src/core/spice/SpiceTimeSeriesNormalizer.mjs +132 -0
  45. package/src/index.mjs +8 -0
  46. package/src/renderers.mjs +29 -0
  47. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
  48. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
  49. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
  50. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
  51. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
  52. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
  53. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
  54. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Normalizes simulator time-series rows to transient directive timing.
3
+ */
4
+ export class SpiceTimeSeriesNormalizer {
5
+ static #EPSILON = 1e-12
6
+
7
+ /**
8
+ * Resamples graph models onto the transient analysis time grid.
9
+ * @param {object[]} graphs Graph models with time and values arrays.
10
+ * @param {{ tstep?: number, tstop?: number, tstart?: number } | null} tran Transient timing parameters.
11
+ * @returns {object[]}
12
+ */
13
+ static resampleGraphs(graphs, tran) {
14
+ const targetTime = SpiceTimeSeriesNormalizer.#targetTimeValues(tran)
15
+ if (!targetTime) return graphs
16
+
17
+ return graphs.map((graph) => ({
18
+ ...graph,
19
+ time: targetTime,
20
+ values: SpiceTimeSeriesNormalizer.#interpolateValues(
21
+ graph.time,
22
+ graph.values,
23
+ targetTime
24
+ )
25
+ }))
26
+ }
27
+
28
+ /**
29
+ * Builds the target transient time grid in seconds.
30
+ * @param {{ tstep?: number, tstop?: number, tstart?: number } | null} tran Transient timing parameters.
31
+ * @returns {number[] | null}
32
+ */
33
+ static #targetTimeValues(tran) {
34
+ const tstep = Number(tran?.tstep)
35
+ const tstop = Number(tran?.tstop)
36
+ const tstart =
37
+ tran?.tstart === undefined || tran?.tstart === null
38
+ ? 0
39
+ : Number(tran.tstart)
40
+
41
+ if (
42
+ !Number.isFinite(tstep) ||
43
+ !Number.isFinite(tstop) ||
44
+ !Number.isFinite(tstart) ||
45
+ tstep <= 0 ||
46
+ tstop < tstart
47
+ ) {
48
+ return null
49
+ }
50
+
51
+ const times = []
52
+ for (
53
+ let time = tstart;
54
+ time <= tstop + SpiceTimeSeriesNormalizer.#EPSILON;
55
+ time += tstep
56
+ ) {
57
+ times.push(SpiceTimeSeriesNormalizer.#round(time))
58
+ }
59
+
60
+ const lastTime = times.at(-1)
61
+ if (
62
+ lastTime !== undefined &&
63
+ Math.abs(lastTime - tstop) > SpiceTimeSeriesNormalizer.#EPSILON
64
+ ) {
65
+ times.push(SpiceTimeSeriesNormalizer.#round(tstop))
66
+ }
67
+
68
+ return times
69
+ }
70
+
71
+ /**
72
+ * Interpolates source values onto a target time grid.
73
+ * @param {number[]} sourceTime Source timestamps in seconds.
74
+ * @param {number[]} sourceValues Source sample values.
75
+ * @param {number[]} targetTime Target timestamps in seconds.
76
+ * @returns {number[]}
77
+ */
78
+ static #interpolateValues(sourceTime, sourceValues, targetTime) {
79
+ if (!sourceTime.length || !sourceValues.length) return []
80
+
81
+ return targetTime.map((time) =>
82
+ SpiceTimeSeriesNormalizer.#interpolateAt(
83
+ sourceTime,
84
+ sourceValues,
85
+ time
86
+ )
87
+ )
88
+ }
89
+
90
+ /**
91
+ * Interpolates one value at a target timestamp.
92
+ * @param {number[]} sourceTime Source timestamps in seconds.
93
+ * @param {number[]} sourceValues Source sample values.
94
+ * @param {number} targetTime Target timestamp in seconds.
95
+ * @returns {number}
96
+ */
97
+ static #interpolateAt(sourceTime, sourceValues, targetTime) {
98
+ if (targetTime <= sourceTime[0]) return sourceValues[0]
99
+
100
+ const lastIndex = Math.min(sourceTime.length, sourceValues.length) - 1
101
+ if (targetTime >= sourceTime[lastIndex]) {
102
+ return sourceValues[lastIndex]
103
+ }
104
+
105
+ for (let index = 1; index <= lastIndex; index += 1) {
106
+ const currentTime = sourceTime[index]
107
+ if (targetTime > currentTime) continue
108
+
109
+ const previousTime = sourceTime[index - 1]
110
+ const previousValue = sourceValues[index - 1]
111
+ const currentValue = sourceValues[index]
112
+ if (currentTime === previousTime) return currentValue
113
+
114
+ const ratio =
115
+ (targetTime - previousTime) / (currentTime - previousTime)
116
+ return SpiceTimeSeriesNormalizer.#round(
117
+ previousValue + ratio * (currentValue - previousValue)
118
+ )
119
+ }
120
+
121
+ return sourceValues[lastIndex]
122
+ }
123
+
124
+ /**
125
+ * Rounds numeric output to a stable precision.
126
+ * @param {number} value Numeric value.
127
+ * @returns {number}
128
+ */
129
+ static #round(value) {
130
+ return Number(Number(value).toPrecision(12))
131
+ }
132
+ }
package/src/index.mjs CHANGED
@@ -1,4 +1,12 @@
1
1
  export { CircuitJsonDocument } from './core/CircuitJsonDocument.mjs'
2
+ export { CircuitJsonBomBuilder } from './core/CircuitJsonBomBuilder.mjs'
3
+ export { CircuitJsonElementValidator } from './core/CircuitJsonElementValidator.mjs'
2
4
  export { CircuitJsonIndexer } from './core/CircuitJsonIndexer.mjs'
5
+ export { CircuitJsonManufacturingBuilder } from './core/CircuitJsonManufacturingBuilder.mjs'
3
6
  export { CircuitJsonParser } from './core/CircuitJsonParser.mjs'
7
+ export { CircuitJsonSourceMetadata } from './core/CircuitJsonSourceMetadata.mjs'
8
+ export { CircuitJsonSupportMatrixBuilder } from './core/CircuitJsonSupportMatrixBuilder.mjs'
4
9
  export { CircuitJsonUnits } from './core/CircuitJsonUnits.mjs'
10
+ export { SpiceCompatibilityPreprocessor } from './core/spice/SpiceCompatibilityPreprocessor.mjs'
11
+ export { SpiceSimulationService } from './core/spice/SpiceSimulationService.mjs'
12
+ export * from './renderers.mjs'
@@ -0,0 +1,29 @@
1
+ export { CircuitJsonManufacturingDownloadBuilder } from './core/CircuitJsonManufacturingDownloadBuilder.mjs'
2
+ export { CircuitJsonPcbClearanceDiagnostics } from './core/CircuitJsonPcbClearanceDiagnostics.mjs'
3
+ export { CircuitJsonPcbCopperGeometry } from './core/CircuitJsonPcbCopperGeometry.mjs'
4
+ export { CircuitJsonPcbDrawingStyle } from './core/CircuitJsonPcbDrawingStyle.mjs'
5
+ export { CircuitJsonPcbHolePrimitiveModel } from './core/CircuitJsonPcbHolePrimitiveModel.mjs'
6
+ export { CircuitJsonPcbNetMetadata } from './core/CircuitJsonPcbNetMetadata.mjs'
7
+ export { CircuitJsonPcbPadPrimitiveModel } from './core/CircuitJsonPcbPadPrimitiveModel.mjs'
8
+ export { CircuitJsonPcbPrimitiveArtwork } from './core/CircuitJsonPcbPrimitiveArtwork.mjs'
9
+ export { CircuitJsonPcbPrimitiveBuilder } from './core/CircuitJsonPcbPrimitiveBuilder.mjs'
10
+ export { CircuitJsonPcbPrimitiveFields } from './core/CircuitJsonPcbPrimitiveFields.mjs'
11
+ export { CircuitJsonPcbPrimitiveGeometry } from './core/CircuitJsonPcbPrimitiveGeometry.mjs'
12
+ export { CircuitJsonPcbPrimitiveGroups } from './core/CircuitJsonPcbPrimitiveGroups.mjs'
13
+ export { CircuitJsonPcbPrimitiveIndex } from './core/CircuitJsonPcbPrimitiveIndex.mjs'
14
+ export { CircuitJsonPcbPrimitiveOverlays } from './core/CircuitJsonPcbPrimitiveOverlays.mjs'
15
+ export { CircuitJsonPcbTraceLengthModel } from './core/CircuitJsonPcbTraceLengthModel.mjs'
16
+ export { CircuitJsonPcbZonePrimitiveBuilder } from './core/CircuitJsonPcbZonePrimitiveBuilder.mjs'
17
+ export { PcbBoundsSelectionModel } from './core/PcbBoundsSelectionModel.mjs'
18
+ export { PcbCandidateSelectionModel } from './core/PcbCandidateSelectionModel.mjs'
19
+ export { PcbDiagnosticFocusModel } from './core/PcbDiagnosticFocusModel.mjs'
20
+ export { PcbInteractionPrimitiveModel } from './core/PcbInteractionPrimitiveModel.mjs'
21
+ export { SelectedPartCircuitJsonExportAdapter } from './core/SelectedPartCircuitJsonExportAdapter.mjs'
22
+ export { CircuitJsonPcbPrimitiveAttributeRenderer } from './ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs'
23
+ export { CircuitJsonPcbSvgRenderer } from './ui/CircuitJsonPcbSvgRenderer.mjs'
24
+ export { CircuitJsonPcbViaSvgRenderer } from './ui/CircuitJsonPcbViaSvgRenderer.mjs'
25
+ export { CircuitJsonSchematicSvgArcPath } from './ui/CircuitJsonSchematicSvgArcPath.mjs'
26
+ export { CircuitJsonSchematicSvgPortMetadata } from './ui/CircuitJsonSchematicSvgPortMetadata.mjs'
27
+ export { CircuitJsonSchematicSvgPrimitiveAttributes } from './ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs'
28
+ export { CircuitJsonSchematicSvgRenderer } from './ui/CircuitJsonSchematicSvgRenderer.mjs'
29
+ export { CircuitJsonSchematicTableSvgRenderer } from './ui/CircuitJsonSchematicTableSvgRenderer.mjs'
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Renders shared SVG attributes for CircuitJSON PCB primitives.
3
+ */
4
+ export class CircuitJsonPcbPrimitiveAttributeRenderer {
5
+ /**
6
+ * Renders data, paint, and style attributes for one primitive.
7
+ * @param {object} primitive Primitive row.
8
+ * @returns {string}
9
+ */
10
+ static render(primitive) {
11
+ const dataAttributes = [
12
+ ['data-pcb-primitive-id', primitive.id],
13
+ ['data-layer', primitive.layer],
14
+ ['data-net', primitive.netName],
15
+ ['data-component-key', primitive.componentKey],
16
+ ['data-footprint-id', primitive.footprintId],
17
+ ['data-pcb-group-ids', (primitive.groupIds || []).join(' ')],
18
+ ['data-subcircuit-ids', (primitive.subcircuitIds || []).join(' ')],
19
+ ['data-source-net-id', primitive.sourceNetId],
20
+ ['data-net-color', primitive.netColor],
21
+ ['data-knockout', primitive.isKnockout ? 'true' : ''],
22
+ ['data-pcb-component-side', this.#componentSide(primitive)],
23
+ [
24
+ 'data-solder-mask-covered',
25
+ primitive.coveredWithSolderMask === undefined ||
26
+ primitive.coveredWithSolderMask === null
27
+ ? ''
28
+ : String(Boolean(primitive.coveredWithSolderMask))
29
+ ],
30
+ ['data-primitive-kind', primitive.kind]
31
+ ]
32
+ .filter((entry) => String(entry[1] || '').trim())
33
+ .map(([name, value]) => name + '="' + this.#escapeHtml(value) + '"')
34
+ const style = this.#styleAttribute(primitive)
35
+ const attributes = [
36
+ ...dataAttributes,
37
+ ...this.#paintAttributes(primitive)
38
+ ]
39
+ if (style) attributes.push(style)
40
+ return attributes.join(' ')
41
+ }
42
+
43
+ /**
44
+ * Resolves a primitive side from explicit side or layer metadata.
45
+ * @param {object} primitive Primitive row.
46
+ * @returns {string}
47
+ */
48
+ static #componentSide(primitive) {
49
+ const side = String(primitive.side || '')
50
+ .trim()
51
+ .toLowerCase()
52
+ if (side === 'top' || side === 'bottom') return side
53
+ const layer = String(primitive.layer || '')
54
+ .trim()
55
+ .toLowerCase()
56
+ return layer === 'top' || layer === 'bottom' ? layer : ''
57
+ }
58
+
59
+ /**
60
+ * Builds an inline style attribute for primitive metadata.
61
+ * @param {object} primitive Primitive row.
62
+ * @returns {string}
63
+ */
64
+ static #styleAttribute(primitive) {
65
+ const netColor = this.#safeColor(primitive.netColor)
66
+ if (!netColor) return ''
67
+ return 'style="--pcb-net-color: ' + netColor + '"'
68
+ }
69
+
70
+ /**
71
+ * Builds explicit SVG paint attributes for documentation primitives.
72
+ * @param {object} primitive Primitive row.
73
+ * @returns {string[]}
74
+ */
75
+ static #paintAttributes(primitive) {
76
+ const attributes = []
77
+ const stroke = this.#safeColor(primitive.strokeColor)
78
+ const fill = this.#safeColor(primitive.fillColor)
79
+ const dashArray = this.#safeDashArray(primitive.dashArray)
80
+ if (stroke) attributes.push('stroke="' + stroke + '"')
81
+ if (fill) attributes.push('fill="' + fill + '"')
82
+ if (dashArray) {
83
+ attributes.push(
84
+ 'stroke-dasharray="' + this.#escapeHtml(dashArray) + '"'
85
+ )
86
+ }
87
+ return attributes
88
+ }
89
+
90
+ /**
91
+ * Returns a color safe for SVG attributes and CSS variables.
92
+ * @param {unknown} value Color candidate.
93
+ * @returns {string}
94
+ */
95
+ static #safeColor(value) {
96
+ const text = String(value || '').trim()
97
+ return /^#[0-9a-f]{3,8}$/iu.test(text) ? text : ''
98
+ }
99
+
100
+ /**
101
+ * Returns a numeric dash array safe for SVG attributes.
102
+ * @param {unknown} value Dash array candidate.
103
+ * @returns {string}
104
+ */
105
+ static #safeDashArray(value) {
106
+ const parts = String(value || '')
107
+ .trim()
108
+ .split(/\s+/u)
109
+ .filter(Boolean)
110
+ if (!parts.length) return ''
111
+ return parts.every((part) => Number.isFinite(Number(part)))
112
+ ? parts.join(' ')
113
+ : ''
114
+ }
115
+
116
+ /**
117
+ * Escapes markup text.
118
+ * @param {unknown} value Raw value.
119
+ * @returns {string}
120
+ */
121
+ static #escapeHtml(value) {
122
+ return String(value ?? '')
123
+ .replaceAll('&', '&amp;')
124
+ .replaceAll('<', '&lt;')
125
+ .replaceAll('>', '&gt;')
126
+ .replaceAll('"', '&quot;')
127
+ }
128
+ }