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.
- package/AGENTS.md +5 -3
- package/README.md +21 -2
- package/docs/api.md +50 -4
- package/docs/model-format.md +21 -3
- package/package.json +3 -2
- package/spec/library-scope.md +4 -1
- package/src/core/CircuitJsonBomBuilder.mjs +143 -0
- package/src/core/CircuitJsonDocument.mjs +46 -13
- package/src/core/CircuitJsonElementValidator.mjs +990 -0
- package/src/core/CircuitJsonIndexer.mjs +773 -4
- package/src/core/CircuitJsonManufacturingBuilder.mjs +898 -0
- package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
- package/src/core/CircuitJsonParser.mjs +22 -6
- 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 +481 -0
- package/src/core/CircuitJsonUnits.mjs +133 -8
- 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/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 +8 -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,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('&', '&')
|
|
124
|
+
.replaceAll('<', '<')
|
|
125
|
+
.replaceAll('>', '>')
|
|
126
|
+
.replaceAll('"', '"')
|
|
127
|
+
}
|
|
128
|
+
}
|