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,172 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from './CircuitJsonUnits.mjs'
|
|
2
|
+
import { CircuitJsonPcbPrimitiveGeometry } from './CircuitJsonPcbPrimitiveGeometry.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes drilled PCB elements into shared primitive geometry.
|
|
6
|
+
*/
|
|
7
|
+
export class CircuitJsonPcbHolePrimitiveModel {
|
|
8
|
+
/**
|
|
9
|
+
* Builds shape and bounds metadata for a drilled PCB element.
|
|
10
|
+
* @param {object} element Drilled PCB element.
|
|
11
|
+
* @param {{ x: number, y: number }} center Drill center.
|
|
12
|
+
* @returns {object}
|
|
13
|
+
*/
|
|
14
|
+
static build(element, center) {
|
|
15
|
+
const shape = CircuitJsonPcbHolePrimitiveModel.#outerShape(element)
|
|
16
|
+
const points = CircuitJsonPcbHolePrimitiveModel.#outerPoints(element)
|
|
17
|
+
const size = CircuitJsonPcbHolePrimitiveModel.#outerSize(element, shape)
|
|
18
|
+
const hole = CircuitJsonPcbHolePrimitiveModel.#holeSize(
|
|
19
|
+
element,
|
|
20
|
+
shape,
|
|
21
|
+
size
|
|
22
|
+
)
|
|
23
|
+
const bounds = points.length
|
|
24
|
+
? CircuitJsonPcbPrimitiveGeometry.pointsBounds(points)
|
|
25
|
+
: CircuitJsonPcbPrimitiveGeometry.centerBounds(
|
|
26
|
+
center,
|
|
27
|
+
size.width,
|
|
28
|
+
size.height
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
shape,
|
|
33
|
+
width: size.width,
|
|
34
|
+
height: size.height,
|
|
35
|
+
diameter: Math.max(size.width, size.height),
|
|
36
|
+
holeShape: hole.shape,
|
|
37
|
+
holeDiameter: hole.diameter,
|
|
38
|
+
holeWidth: hole.width,
|
|
39
|
+
holeHeight: hole.height,
|
|
40
|
+
rotation: CircuitJsonUnits.angle(
|
|
41
|
+
element.ccw_rotation ?? element.rotation,
|
|
42
|
+
0
|
|
43
|
+
),
|
|
44
|
+
points,
|
|
45
|
+
bounds
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolves the visible copper or board hole shape.
|
|
51
|
+
* @param {object} element Drilled PCB element.
|
|
52
|
+
* @returns {'circle' | 'pill' | 'polygon' | 'rect'}
|
|
53
|
+
*/
|
|
54
|
+
static #outerShape(element) {
|
|
55
|
+
const raw = CircuitJsonPcbHolePrimitiveModel.#shapeText(
|
|
56
|
+
element.shape || element.hole_shape
|
|
57
|
+
)
|
|
58
|
+
if (raw.includes('polygon')) return 'polygon'
|
|
59
|
+
if (raw.includes('rect')) return 'rect'
|
|
60
|
+
if (
|
|
61
|
+
raw.includes('pill') ||
|
|
62
|
+
raw.includes('slot') ||
|
|
63
|
+
raw.includes('oval')
|
|
64
|
+
)
|
|
65
|
+
return 'pill'
|
|
66
|
+
return 'circle'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolves the outer rendered size.
|
|
71
|
+
* @param {object} element Drilled PCB element.
|
|
72
|
+
* @param {string} shape Normalized outer shape.
|
|
73
|
+
* @returns {{ width: number, height: number }}
|
|
74
|
+
*/
|
|
75
|
+
static #outerSize(element, shape) {
|
|
76
|
+
const diameter = CircuitJsonUnits.length(
|
|
77
|
+
element.diameter ?? element.outer_diameter,
|
|
78
|
+
0.6
|
|
79
|
+
)
|
|
80
|
+
const width = CircuitJsonUnits.length(
|
|
81
|
+
element.rect_pad_width ?? element.pad_width ?? element.width,
|
|
82
|
+
diameter
|
|
83
|
+
)
|
|
84
|
+
const height = CircuitJsonUnits.length(
|
|
85
|
+
element.rect_pad_height ?? element.pad_height ?? element.height,
|
|
86
|
+
shape === 'circle' ? width : diameter
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
width,
|
|
91
|
+
height: shape === 'circle' ? width : height
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolves polygonal outer pad points.
|
|
97
|
+
* @param {object} element Drilled PCB element.
|
|
98
|
+
* @returns {{ x: number, y: number }[]}
|
|
99
|
+
*/
|
|
100
|
+
static #outerPoints(element) {
|
|
101
|
+
return (Array.isArray(element?.pad_outline) ? element.pad_outline : [])
|
|
102
|
+
.map((point) => CircuitJsonUnits.optionalPoint(point))
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolves the drilled opening size.
|
|
108
|
+
* @param {object} element Drilled PCB element.
|
|
109
|
+
* @param {string} outerShape Normalized outer shape.
|
|
110
|
+
* @param {{ width: number, height: number }} outerSize Outer size.
|
|
111
|
+
* @returns {{ shape: 'circle' | 'pill' | 'rect', diameter: number, width: number, height: number }}
|
|
112
|
+
*/
|
|
113
|
+
static #holeSize(element, outerShape, outerSize) {
|
|
114
|
+
const shape = CircuitJsonPcbHolePrimitiveModel.#holeShape(
|
|
115
|
+
element,
|
|
116
|
+
outerShape
|
|
117
|
+
)
|
|
118
|
+
const fallbackDiameter =
|
|
119
|
+
shape === 'circle'
|
|
120
|
+
? Math.min(outerSize.width, outerSize.height) * 0.45
|
|
121
|
+
: Math.min(outerSize.width, outerSize.height)
|
|
122
|
+
const diameter = CircuitJsonUnits.length(
|
|
123
|
+
element.hole_diameter ??
|
|
124
|
+
element.holeDiameter ??
|
|
125
|
+
element.drill_diameter,
|
|
126
|
+
fallbackDiameter
|
|
127
|
+
)
|
|
128
|
+
const width = CircuitJsonUnits.length(
|
|
129
|
+
element.hole_width,
|
|
130
|
+
shape === 'circle' ? diameter : outerSize.width
|
|
131
|
+
)
|
|
132
|
+
const height = CircuitJsonUnits.length(
|
|
133
|
+
element.hole_height,
|
|
134
|
+
shape === 'circle' ? diameter : outerSize.height
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return { shape, diameter, width, height }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolves the drilled opening shape.
|
|
142
|
+
* @param {object} element Drilled PCB element.
|
|
143
|
+
* @param {string} outerShape Normalized outer shape.
|
|
144
|
+
* @returns {'circle' | 'pill' | 'rect'}
|
|
145
|
+
*/
|
|
146
|
+
static #holeShape(element, outerShape) {
|
|
147
|
+
const raw = CircuitJsonPcbHolePrimitiveModel.#shapeText(
|
|
148
|
+
element.hole_shape || element.shape
|
|
149
|
+
)
|
|
150
|
+
if (raw === 'round') return 'circle'
|
|
151
|
+
if (raw.includes('rect') && !raw.includes('rect_pad')) return 'rect'
|
|
152
|
+
if (
|
|
153
|
+
raw.includes('pill') ||
|
|
154
|
+
raw.includes('slot') ||
|
|
155
|
+
raw.includes('oval')
|
|
156
|
+
)
|
|
157
|
+
return 'pill'
|
|
158
|
+
if (raw.includes('circular') || raw.includes('circle')) return 'circle'
|
|
159
|
+
return element.type === 'pcb_hole' ? outerShape : 'circle'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Normalizes a shape string.
|
|
164
|
+
* @param {unknown} value Shape value.
|
|
165
|
+
* @returns {string}
|
|
166
|
+
*/
|
|
167
|
+
static #shapeText(value) {
|
|
168
|
+
return String(value || '')
|
|
169
|
+
.trim()
|
|
170
|
+
.toLowerCase()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes PCB net metadata and applies it to PCB primitive rows.
|
|
3
|
+
*/
|
|
4
|
+
export class CircuitJsonPcbNetMetadata {
|
|
5
|
+
/**
|
|
6
|
+
* Decorates primitive rows with explicit net metadata when available.
|
|
7
|
+
* @param {object[]} primitives Primitive rows.
|
|
8
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
9
|
+
* @returns {object[]}
|
|
10
|
+
*/
|
|
11
|
+
static decoratePrimitives(primitives, index) {
|
|
12
|
+
const lookup = CircuitJsonPcbNetMetadata.#lookup(index)
|
|
13
|
+
return primitives.map((primitive) =>
|
|
14
|
+
CircuitJsonPcbNetMetadata.#decoratePrimitive(primitive, lookup)
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Builds unique net rows from explicit metadata and primitive usage.
|
|
20
|
+
* @param {object[]} primitives Primitive rows.
|
|
21
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
22
|
+
* @returns {object[]}
|
|
23
|
+
*/
|
|
24
|
+
static nets(primitives, index) {
|
|
25
|
+
const entries = CircuitJsonPcbNetMetadata.#entries(index)
|
|
26
|
+
const byName = new Map(entries.map((entry) => [entry.name, entry]))
|
|
27
|
+
|
|
28
|
+
for (const primitive of primitives) {
|
|
29
|
+
const name = String(primitive.netName || '').trim()
|
|
30
|
+
if (name && !byName.has(name)) byName.set(name, { name })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return [...byName.values()]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Applies metadata to one primitive row.
|
|
38
|
+
* @param {object} primitive Primitive row.
|
|
39
|
+
* @param {Map<string, object>} lookup Net lookup map.
|
|
40
|
+
* @returns {object}
|
|
41
|
+
*/
|
|
42
|
+
static #decoratePrimitive(primitive, lookup) {
|
|
43
|
+
const entry = CircuitJsonPcbNetMetadata.#primitiveNetEntry(
|
|
44
|
+
primitive,
|
|
45
|
+
lookup
|
|
46
|
+
)
|
|
47
|
+
if (!entry) return primitive
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...primitive,
|
|
51
|
+
netName: entry.name || primitive.netName,
|
|
52
|
+
sourceNetId: entry.sourceNetId || primitive.sourceNetId || '',
|
|
53
|
+
pcbNetId: entry.pcbNetId || primitive.pcbNetId || '',
|
|
54
|
+
groupIds: CircuitJsonPcbNetMetadata.#uniqueStrings([
|
|
55
|
+
...(primitive.groupIds || []),
|
|
56
|
+
...(entry.groupIds || [])
|
|
57
|
+
]),
|
|
58
|
+
...(entry.highlightColor ? { netColor: entry.highlightColor } : {})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolves the explicit net entry matching one primitive.
|
|
64
|
+
* @param {object} primitive Primitive row.
|
|
65
|
+
* @param {Map<string, object>} lookup Net lookup map.
|
|
66
|
+
* @returns {object | null}
|
|
67
|
+
*/
|
|
68
|
+
static #primitiveNetEntry(primitive, lookup) {
|
|
69
|
+
for (const candidate of [
|
|
70
|
+
primitive.netName,
|
|
71
|
+
primitive.sourceNetId,
|
|
72
|
+
primitive.pcbNetId,
|
|
73
|
+
primitive.source?.source_net_id,
|
|
74
|
+
primitive.source?.pcb_net_id
|
|
75
|
+
]) {
|
|
76
|
+
const key = String(candidate || '').trim()
|
|
77
|
+
if (key && lookup.has(key)) return lookup.get(key)
|
|
78
|
+
}
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds a lookup keyed by net names and source IDs.
|
|
84
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
85
|
+
* @returns {Map<string, object>}
|
|
86
|
+
*/
|
|
87
|
+
static #lookup(index) {
|
|
88
|
+
const lookup = new Map()
|
|
89
|
+
for (const entry of CircuitJsonPcbNetMetadata.#entries(index)) {
|
|
90
|
+
for (const key of [entry.name, entry.sourceNetId, entry.pcbNetId]) {
|
|
91
|
+
const text = String(key || '').trim()
|
|
92
|
+
if (text) lookup.set(text, entry)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return lookup
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Builds explicit net entries from source and PCB net rows.
|
|
100
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
101
|
+
* @returns {object[]}
|
|
102
|
+
*/
|
|
103
|
+
static #entries(index) {
|
|
104
|
+
const sourceRows = CircuitJsonPcbNetMetadata.#all(index, 'source_net')
|
|
105
|
+
const pcbRows = CircuitJsonPcbNetMetadata.#all(index, 'pcb_net')
|
|
106
|
+
const pcbBySourceId = new Map(
|
|
107
|
+
pcbRows
|
|
108
|
+
.map((row) => [String(row.source_net_id || '').trim(), row])
|
|
109
|
+
.filter(([id]) => id)
|
|
110
|
+
)
|
|
111
|
+
const entries = []
|
|
112
|
+
|
|
113
|
+
for (const source of sourceRows) {
|
|
114
|
+
const sourceNetId = String(source.source_net_id || '').trim()
|
|
115
|
+
const pcbNet = pcbBySourceId.get(sourceNetId) || {}
|
|
116
|
+
const entry = CircuitJsonPcbNetMetadata.#entry(source, pcbNet)
|
|
117
|
+
if (entry) entries.push(entry)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const pcbNet of pcbRows) {
|
|
121
|
+
const sourceNetId = String(pcbNet.source_net_id || '').trim()
|
|
122
|
+
if (sourceNetId && pcbBySourceId.has(sourceNetId)) continue
|
|
123
|
+
const entry = CircuitJsonPcbNetMetadata.#entry({}, pcbNet)
|
|
124
|
+
if (entry) entries.push(entry)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return entries
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Builds one explicit net entry.
|
|
132
|
+
* @param {object} source Source net row.
|
|
133
|
+
* @param {object} pcbNet PCB net row.
|
|
134
|
+
* @returns {object | null}
|
|
135
|
+
*/
|
|
136
|
+
static #entry(source, pcbNet) {
|
|
137
|
+
const sourceNetId = String(
|
|
138
|
+
source.source_net_id || pcbNet.source_net_id || ''
|
|
139
|
+
).trim()
|
|
140
|
+
const pcbNetId = String(pcbNet.pcb_net_id || '').trim()
|
|
141
|
+
const name = String(
|
|
142
|
+
pcbNet.name ||
|
|
143
|
+
pcbNet.net ||
|
|
144
|
+
source.name ||
|
|
145
|
+
source.net ||
|
|
146
|
+
sourceNetId ||
|
|
147
|
+
pcbNetId ||
|
|
148
|
+
''
|
|
149
|
+
).trim()
|
|
150
|
+
if (!name) return null
|
|
151
|
+
|
|
152
|
+
return CircuitJsonPcbNetMetadata.#clean({
|
|
153
|
+
name,
|
|
154
|
+
sourceNetId,
|
|
155
|
+
pcbNetId,
|
|
156
|
+
groupIds: CircuitJsonPcbNetMetadata.#groupIds(source, pcbNet),
|
|
157
|
+
highlightColor: CircuitJsonPcbNetMetadata.#safeColor(
|
|
158
|
+
pcbNet.highlight_color || pcbNet.highlightColor || pcbNet.color
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolves group ids from explicit net metadata.
|
|
165
|
+
* @param {object} source Source net row.
|
|
166
|
+
* @param {object} pcbNet PCB net row.
|
|
167
|
+
* @returns {string[]}
|
|
168
|
+
*/
|
|
169
|
+
static #groupIds(source, pcbNet) {
|
|
170
|
+
return CircuitJsonPcbNetMetadata.#uniqueStrings(
|
|
171
|
+
[source, pcbNet].flatMap((row) => [
|
|
172
|
+
row?.source_group_id,
|
|
173
|
+
row?.pcb_group_id,
|
|
174
|
+
row?.schematic_group_id,
|
|
175
|
+
row?.group_id,
|
|
176
|
+
row?.member_source_group_id,
|
|
177
|
+
row?.member_pcb_group_id,
|
|
178
|
+
row?.member_schematic_group_id,
|
|
179
|
+
row?.member_group_id,
|
|
180
|
+
...CircuitJsonPcbNetMetadata.#arrayValues(row?.group_ids),
|
|
181
|
+
...CircuitJsonPcbNetMetadata.#arrayValues(
|
|
182
|
+
row?.member_source_group_ids
|
|
183
|
+
),
|
|
184
|
+
...CircuitJsonPcbNetMetadata.#arrayValues(
|
|
185
|
+
row?.member_pcb_group_ids
|
|
186
|
+
),
|
|
187
|
+
...CircuitJsonPcbNetMetadata.#arrayValues(
|
|
188
|
+
row?.member_schematic_group_ids
|
|
189
|
+
),
|
|
190
|
+
...CircuitJsonPcbNetMetadata.#arrayValues(row?.member_group_ids)
|
|
191
|
+
])
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Returns array values when the candidate is an array.
|
|
197
|
+
* @param {unknown} value Candidate value.
|
|
198
|
+
* @returns {unknown[]}
|
|
199
|
+
*/
|
|
200
|
+
static #arrayValues(value) {
|
|
201
|
+
return Array.isArray(value) ? value : []
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolves unique non-empty strings.
|
|
206
|
+
* @param {unknown[]} values Candidate values.
|
|
207
|
+
* @returns {string[]}
|
|
208
|
+
*/
|
|
209
|
+
static #uniqueStrings(values) {
|
|
210
|
+
return [...new Set(values.map((value) => String(value || '').trim()))]
|
|
211
|
+
.filter(Boolean)
|
|
212
|
+
.sort((left, right) => left.localeCompare(right))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Removes empty metadata fields.
|
|
217
|
+
* @param {object} entry Net entry.
|
|
218
|
+
* @returns {object}
|
|
219
|
+
*/
|
|
220
|
+
static #clean(entry) {
|
|
221
|
+
return Object.fromEntries(
|
|
222
|
+
Object.entries(entry).filter(([_key, value]) =>
|
|
223
|
+
String(value || '').trim()
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Resolves indexed element rows.
|
|
230
|
+
* @param {{ elementsByType: Map<string, object[]> }} index Element index.
|
|
231
|
+
* @param {string} type Element type.
|
|
232
|
+
* @returns {object[]}
|
|
233
|
+
*/
|
|
234
|
+
static #all(index, type) {
|
|
235
|
+
return index.elementsByType.get(type) || []
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Returns a color safe for SVG attributes and CSS variables.
|
|
240
|
+
* @param {unknown} value Color candidate.
|
|
241
|
+
* @returns {string}
|
|
242
|
+
*/
|
|
243
|
+
static #safeColor(value) {
|
|
244
|
+
const text = String(value || '').trim()
|
|
245
|
+
return /^#[0-9a-f]{3,8}$/iu.test(text) ? text : ''
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CircuitJsonUnits } from './CircuitJsonUnits.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes SMT pad shape metadata for PCB primitive builders.
|
|
5
|
+
*/
|
|
6
|
+
export class CircuitJsonPcbPadPrimitiveModel {
|
|
7
|
+
/**
|
|
8
|
+
* Resolves a normalized SMT pad shape value.
|
|
9
|
+
* @param {object} element Pad element.
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
static shape(element) {
|
|
13
|
+
return String(element.shape || 'rect')
|
|
14
|
+
.trim()
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolves a pad corner radius from explicit or shape-derived metadata.
|
|
20
|
+
* @param {object} element Pad element.
|
|
21
|
+
* @param {string} shape Normalized pad shape.
|
|
22
|
+
* @param {number} width Pad width.
|
|
23
|
+
* @param {number} height Pad height.
|
|
24
|
+
* @returns {number}
|
|
25
|
+
*/
|
|
26
|
+
static radius(element, shape, width, height) {
|
|
27
|
+
const explicitRadius =
|
|
28
|
+
CircuitJsonPcbPadPrimitiveModel.explicitRadius(element)
|
|
29
|
+
return (
|
|
30
|
+
explicitRadius ??
|
|
31
|
+
CircuitJsonPcbPadPrimitiveModel.#defaultRadius(shape, width, height)
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves an explicit pad corner radius.
|
|
37
|
+
* @param {object} element Pad element.
|
|
38
|
+
* @returns {number | null}
|
|
39
|
+
*/
|
|
40
|
+
static explicitRadius(element) {
|
|
41
|
+
return CircuitJsonUnits.optionalLength(
|
|
42
|
+
element.radius ??
|
|
43
|
+
element.corner_radius ??
|
|
44
|
+
element.cornerRadius ??
|
|
45
|
+
element.border_radius ??
|
|
46
|
+
element.borderRadius
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolves a default corner radius for pad shape variants.
|
|
52
|
+
* @param {string} shape Normalized pad shape.
|
|
53
|
+
* @param {number} width Pad width.
|
|
54
|
+
* @param {number} height Pad height.
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
static #defaultRadius(shape, width, height) {
|
|
58
|
+
if (
|
|
59
|
+
shape === 'circle' ||
|
|
60
|
+
shape === 'pill' ||
|
|
61
|
+
shape === 'rotated_pill'
|
|
62
|
+
) {
|
|
63
|
+
return Math.min(width, height) / 2
|
|
64
|
+
}
|
|
65
|
+
if (shape === 'rounded_rect') {
|
|
66
|
+
return Math.min(width, height) * 0.18
|
|
67
|
+
}
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
}
|