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,250 @@
1
+ import { PcbInteractionPrimitiveModel } from './PcbInteractionPrimitiveModel.mjs'
2
+ import { PcbCandidateSelectionModel } from './PcbCandidateSelectionModel.mjs'
3
+
4
+ /**
5
+ * Resolves PCB primitives contained by a measured board-space rectangle.
6
+ */
7
+ export class PcbBoundsSelectionModel {
8
+ /**
9
+ * Resolves contained candidates and unique component/net selections.
10
+ * @param {object | object[]} documentModel Parsed PCB document model.
11
+ * @param {{ minX?: unknown, minY?: unknown, maxX?: unknown, maxY?: unknown }} bounds Board-space bounds.
12
+ * @param {{ side?: string, hiddenLayers?: string[], hiddenObjects?: string[] }} [options] Selection options.
13
+ * @returns {{ bounds: object | null, point: object | null, candidates: object[], selectedCandidate: object | null, componentKeys: string[], netNames: string[] }}
14
+ */
15
+ static resolve(documentModel, bounds, options = {}) {
16
+ const normalizedBounds = PcbBoundsSelectionModel.normalizeBounds(bounds)
17
+ if (!normalizedBounds) return PcbBoundsSelectionModel.#empty()
18
+
19
+ const candidates = PcbBoundsSelectionModel.#containedPrimitives(
20
+ documentModel,
21
+ normalizedBounds,
22
+ options
23
+ ).map((primitive) =>
24
+ PcbCandidateSelectionModel.fromPrimitive(primitive)
25
+ )
26
+ const uniqueCandidates =
27
+ PcbBoundsSelectionModel.#uniqueCandidates(candidates)
28
+ const selectedCandidate =
29
+ PcbCandidateSelectionModel.selectedCandidate(uniqueCandidates)
30
+
31
+ return {
32
+ bounds: normalizedBounds,
33
+ point: PcbBoundsSelectionModel.#center(normalizedBounds),
34
+ candidates: uniqueCandidates,
35
+ selectedCandidate,
36
+ componentKeys: PcbBoundsSelectionModel.#uniqueStrings(
37
+ uniqueCandidates.map((candidate) => candidate.componentKey)
38
+ ),
39
+ netNames: PcbBoundsSelectionModel.#uniqueStrings(
40
+ uniqueCandidates.map((candidate) =>
41
+ PcbCandidateSelectionModel.netName(candidate)
42
+ )
43
+ )
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Normalizes board-space bounds.
49
+ * @param {{ minX?: unknown, minY?: unknown, maxX?: unknown, maxY?: unknown } | null | undefined} bounds Bounds candidate.
50
+ * @returns {{ minX: number, minY: number, maxX: number, maxY: number, width: number, height: number } | null}
51
+ */
52
+ static normalizeBounds(bounds) {
53
+ const minX = PcbBoundsSelectionModel.#finite(bounds?.minX)
54
+ const minY = PcbBoundsSelectionModel.#finite(bounds?.minY)
55
+ const maxX = PcbBoundsSelectionModel.#finite(bounds?.maxX)
56
+ const maxY = PcbBoundsSelectionModel.#finite(bounds?.maxY)
57
+ if ([minX, minY, maxX, maxY].some((value) => value === null)) {
58
+ return null
59
+ }
60
+
61
+ const left = Math.min(minX, maxX)
62
+ const right = Math.max(minX, maxX)
63
+ const top = Math.min(minY, maxY)
64
+ const bottom = Math.max(minY, maxY)
65
+
66
+ return {
67
+ minX: left,
68
+ minY: top,
69
+ maxX: right,
70
+ maxY: bottom,
71
+ width: right - left,
72
+ height: bottom - top
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Builds an empty selection result.
78
+ * @returns {{ bounds: null, point: null, candidates: object[], selectedCandidate: null, componentKeys: string[], netNames: string[] }}
79
+ */
80
+ static #empty() {
81
+ return {
82
+ bounds: null,
83
+ point: null,
84
+ candidates: [],
85
+ selectedCandidate: null,
86
+ componentKeys: [],
87
+ netNames: []
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Returns primitives that are visible and touch the measured bounds.
93
+ * @param {object | object[]} documentModel Parsed PCB document model.
94
+ * @param {object} bounds Normalized bounds.
95
+ * @param {object} options Selection options.
96
+ * @returns {object[]}
97
+ */
98
+ static #containedPrimitives(documentModel, bounds, options) {
99
+ return PcbInteractionPrimitiveModel.build(
100
+ documentModel
101
+ ).primitives.filter(
102
+ (primitive) =>
103
+ PcbBoundsSelectionModel.#isVisible(primitive, options) &&
104
+ PcbBoundsSelectionModel.#touchesBounds(primitive, bounds)
105
+ )
106
+ }
107
+
108
+ /**
109
+ * Returns true when a primitive should participate in area selection.
110
+ * @param {object} primitive Primitive row.
111
+ * @param {{ side?: string, hiddenLayers?: string[], hiddenObjects?: string[] }} options Selection options.
112
+ * @returns {boolean}
113
+ */
114
+ static #isVisible(primitive, options) {
115
+ const side = String(options?.side || '')
116
+ if (side && primitive.side && primitive.side !== side) return false
117
+
118
+ const hiddenLayers = new Set(
119
+ (Array.isArray(options?.hiddenLayers) ? options.hiddenLayers : [])
120
+ .map(String)
121
+ .filter(Boolean)
122
+ )
123
+ if (primitive.layer && hiddenLayers.has(String(primitive.layer))) {
124
+ return false
125
+ }
126
+
127
+ const hiddenObjects = new Set(
128
+ (Array.isArray(options?.hiddenObjects) ? options.hiddenObjects : [])
129
+ .map(String)
130
+ .filter(Boolean)
131
+ )
132
+ return !hiddenObjects.has(PcbBoundsSelectionModel.#objectKey(primitive))
133
+ }
134
+
135
+ /**
136
+ * Returns true when a primitive overlaps or sits inside bounds.
137
+ * @param {object} primitive Primitive row.
138
+ * @param {object} bounds Normalized bounds.
139
+ * @returns {boolean}
140
+ */
141
+ static #touchesBounds(primitive, bounds) {
142
+ const primitiveBounds =
143
+ PcbBoundsSelectionModel.normalizeBounds(primitive?.bounds) ||
144
+ PcbBoundsSelectionModel.#pointBounds(primitive)
145
+ if (!primitiveBounds) return false
146
+
147
+ return !(
148
+ primitiveBounds.maxX < bounds.minX ||
149
+ primitiveBounds.minX > bounds.maxX ||
150
+ primitiveBounds.maxY < bounds.minY ||
151
+ primitiveBounds.minY > bounds.maxY
152
+ )
153
+ }
154
+
155
+ /**
156
+ * Builds zero-area bounds around primitive center coordinates.
157
+ * @param {object} primitive Primitive row.
158
+ * @returns {object | null}
159
+ */
160
+ static #pointBounds(primitive) {
161
+ const x = PcbBoundsSelectionModel.#finite(primitive?.x)
162
+ const y = PcbBoundsSelectionModel.#finite(primitive?.y)
163
+ if (x === null || y === null) return null
164
+ return PcbBoundsSelectionModel.normalizeBounds({
165
+ minX: x,
166
+ minY: y,
167
+ maxX: x,
168
+ maxY: y
169
+ })
170
+ }
171
+
172
+ /**
173
+ * Deduplicates candidates by visible identity.
174
+ * @param {object[]} candidates Candidate rows.
175
+ * @returns {object[]}
176
+ */
177
+ static #uniqueCandidates(candidates) {
178
+ const seen = new Set()
179
+ const unique = []
180
+ for (const candidate of candidates) {
181
+ const key = [
182
+ candidate.kind,
183
+ candidate.componentKey,
184
+ PcbCandidateSelectionModel.netName(candidate),
185
+ candidate.layer
186
+ ].join('|')
187
+ if (seen.has(key)) continue
188
+ seen.add(key)
189
+ unique.push(candidate)
190
+ }
191
+ return unique
192
+ }
193
+
194
+ /**
195
+ * Deduplicates non-empty strings in source order.
196
+ * @param {string[]} values Candidate values.
197
+ * @returns {string[]}
198
+ */
199
+ static #uniqueStrings(values) {
200
+ return [
201
+ ...new Set(values.map(String).map((value) => value.trim()))
202
+ ].filter(Boolean)
203
+ }
204
+
205
+ /**
206
+ * Resolves the sidebar object key for a primitive.
207
+ * @param {object} primitive Primitive row.
208
+ * @returns {string}
209
+ */
210
+ static #objectKey(primitive) {
211
+ return (
212
+ {
213
+ board: 'page',
214
+ cutout: 'page',
215
+ pad: 'pads',
216
+ track: 'tracks',
217
+ via: 'vias',
218
+ zone: 'zones',
219
+ 'copper-text': 'footprint-text',
220
+ copper_text: 'footprint-text',
221
+ silkscreen: 'footprint-text',
222
+ silkscreen_text: 'footprint-text',
223
+ fabrication: 'footprint-text'
224
+ }[primitive?.kind] || String(primitive?.kind || '')
225
+ )
226
+ }
227
+
228
+ /**
229
+ * Resolves the center point for normalized bounds.
230
+ * @param {object} bounds Normalized bounds.
231
+ * @returns {{ x: number, y: number }}
232
+ */
233
+ static #center(bounds) {
234
+ return {
235
+ x: (bounds.minX + bounds.maxX) / 2,
236
+ y: (bounds.minY + bounds.maxY) / 2
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Converts a value to a finite number or null.
242
+ * @param {unknown} value Numeric candidate.
243
+ * @returns {number | null}
244
+ */
245
+ static #finite(value) {
246
+ if (value === undefined || value === null || value === '') return null
247
+ const number = Number(value)
248
+ return Number.isFinite(number) ? number : null
249
+ }
250
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Normalizes PCB hit candidates for shared click, hover, and bounds workflows.
3
+ */
4
+ export class PcbCandidateSelectionModel {
5
+ /**
6
+ * Returns the first component-backed candidate.
7
+ * @param {object[]} candidates Hit-test candidates.
8
+ * @returns {object | null}
9
+ */
10
+ static componentCandidate(candidates) {
11
+ return (
12
+ (Array.isArray(candidates) ? candidates : []).find((candidate) =>
13
+ String(candidate?.componentKey || '').trim()
14
+ ) || null
15
+ )
16
+ }
17
+
18
+ /**
19
+ * Returns the first net-backed candidate.
20
+ * @param {object[]} candidates Hit-test candidates.
21
+ * @returns {object | null}
22
+ */
23
+ static netCandidate(candidates) {
24
+ return (
25
+ (Array.isArray(candidates) ? candidates : []).find((candidate) =>
26
+ PcbCandidateSelectionModel.netName(candidate)
27
+ ) || null
28
+ )
29
+ }
30
+
31
+ /**
32
+ * Returns the candidate chosen for user-facing selection.
33
+ * @param {object[]} candidates Hit-test candidates.
34
+ * @returns {object | null}
35
+ */
36
+ static selectedCandidate(candidates) {
37
+ const rows = Array.isArray(candidates) ? candidates : []
38
+ return (
39
+ PcbCandidateSelectionModel.componentCandidate(rows) ||
40
+ PcbCandidateSelectionModel.netCandidate(rows) ||
41
+ rows[0] ||
42
+ null
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Returns one candidate's net name.
48
+ * @param {object | null} candidate Hit-test candidate.
49
+ * @returns {string}
50
+ */
51
+ static netName(candidate) {
52
+ return String(
53
+ candidate?.netName ?? candidate?.net ?? candidate?.net_name ?? ''
54
+ ).trim()
55
+ }
56
+
57
+ /**
58
+ * Builds a normalized candidate from a primitive row.
59
+ * @param {object} primitive Primitive row.
60
+ * @returns {object}
61
+ */
62
+ static fromPrimitive(primitive) {
63
+ return {
64
+ role: String(primitive?.kind || ''),
65
+ kind: String(primitive?.kind || ''),
66
+ componentKey: String(primitive?.componentKey || ''),
67
+ componentId: String(
68
+ primitive?.componentId || primitive?.componentKey || ''
69
+ ),
70
+ netName: String(primitive?.netName || ''),
71
+ net: String(primitive?.netName || ''),
72
+ layer: String(primitive?.layer || ''),
73
+ layerKey: String(primitive?.layer || ''),
74
+ source: primitive?.source || primitive || null
75
+ }
76
+ }
77
+ }