circuitjson-toolkit 1.0.10 → 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/docs/model-format.md +15 -0
- package/package.json +3 -2
- package/src/core/CircuitJsonBomBuilder.mjs +22 -25
- package/src/core/CircuitJsonElementValidator.mjs +233 -16
- package/src/core/CircuitJsonIndexer.mjs +510 -5
- package/src/core/CircuitJsonManufacturingBuilder.mjs +488 -16
- package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
- 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 +227 -5
- 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/index.mjs +2 -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
|
@@ -9,13 +9,17 @@ export class CircuitJsonManufacturingBuilder {
|
|
|
9
9
|
* Builds pick-and-place rows and routing exchange text.
|
|
10
10
|
* @param {object[]} circuitJson Parsed element array.
|
|
11
11
|
* @param {{ elementsByType?: Map<string, object[]>, sourceComponentById?: Map<string, object> }} [index] Optional index.
|
|
12
|
-
* @returns {{ pickAndPlaceRows: object[], routingDsn: string }}
|
|
12
|
+
* @returns {{ pickAndPlaceRows: object[], routingDsn: string, routingGuides: object[], fabricationNotes: object[] }}
|
|
13
13
|
*/
|
|
14
14
|
static build(circuitJson, index = CircuitJsonIndexer.index(circuitJson)) {
|
|
15
15
|
return {
|
|
16
16
|
pickAndPlaceRows:
|
|
17
17
|
CircuitJsonManufacturingBuilder.#pickAndPlaceRows(index),
|
|
18
|
-
routingDsn: CircuitJsonManufacturingBuilder.#routingDsn(index)
|
|
18
|
+
routingDsn: CircuitJsonManufacturingBuilder.#routingDsn(index),
|
|
19
|
+
routingGuides:
|
|
20
|
+
CircuitJsonManufacturingBuilder.#routingGuides(index),
|
|
21
|
+
fabricationNotes:
|
|
22
|
+
CircuitJsonManufacturingBuilder.#fabricationNotes(index)
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -164,6 +168,8 @@ export class CircuitJsonManufacturingBuilder {
|
|
|
164
168
|
* @returns {string[]}
|
|
165
169
|
*/
|
|
166
170
|
static #networkLines(index) {
|
|
171
|
+
const sourceNetNames =
|
|
172
|
+
CircuitJsonManufacturingBuilder.#sourceNetNames(index)
|
|
167
173
|
return [
|
|
168
174
|
' (network',
|
|
169
175
|
...CircuitJsonManufacturingBuilder.#netNames(index).flatMap(
|
|
@@ -172,11 +178,18 @@ export class CircuitJsonManufacturingBuilder {
|
|
|
172
178
|
CircuitJsonManufacturingBuilder.#token(netName),
|
|
173
179
|
...CircuitJsonManufacturingBuilder.#pinLines(
|
|
174
180
|
index,
|
|
175
|
-
netName
|
|
181
|
+
netName,
|
|
182
|
+
sourceNetNames
|
|
183
|
+
),
|
|
184
|
+
...CircuitJsonManufacturingBuilder.#drillLines(
|
|
185
|
+
index,
|
|
186
|
+
netName,
|
|
187
|
+
sourceNetNames
|
|
176
188
|
),
|
|
177
189
|
...CircuitJsonManufacturingBuilder.#wireLines(
|
|
178
190
|
index,
|
|
179
|
-
netName
|
|
191
|
+
netName,
|
|
192
|
+
sourceNetNames
|
|
180
193
|
),
|
|
181
194
|
' )'
|
|
182
195
|
]
|
|
@@ -189,13 +202,17 @@ export class CircuitJsonManufacturingBuilder {
|
|
|
189
202
|
* Builds pad pin lines for one net.
|
|
190
203
|
* @param {{ elementsByType?: Map<string, object[]> }} index Element index.
|
|
191
204
|
* @param {string} netName Net name.
|
|
205
|
+
* @param {Map<string, string>} sourceNetNames Source net lookup.
|
|
192
206
|
* @returns {string[]}
|
|
193
207
|
*/
|
|
194
|
-
static #pinLines(index, netName) {
|
|
208
|
+
static #pinLines(index, netName, sourceNetNames) {
|
|
195
209
|
return CircuitJsonManufacturingBuilder.#all(index, 'pcb_smtpad')
|
|
196
210
|
.filter(
|
|
197
211
|
(pad) =>
|
|
198
|
-
CircuitJsonManufacturingBuilder.#netName(
|
|
212
|
+
CircuitJsonManufacturingBuilder.#netName(
|
|
213
|
+
pad,
|
|
214
|
+
sourceNetNames
|
|
215
|
+
) === netName
|
|
199
216
|
)
|
|
200
217
|
.map((pad) => {
|
|
201
218
|
const point = CircuitJsonUnits.optionalPoint(
|
|
@@ -219,17 +236,92 @@ export class CircuitJsonManufacturingBuilder {
|
|
|
219
236
|
})
|
|
220
237
|
}
|
|
221
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Builds drill feature lines for one net.
|
|
241
|
+
* @param {{ elementsByType?: Map<string, object[]> }} index Element index.
|
|
242
|
+
* @param {string} netName Net name.
|
|
243
|
+
* @param {Map<string, string>} sourceNetNames Source net lookup.
|
|
244
|
+
* @returns {string[]}
|
|
245
|
+
*/
|
|
246
|
+
static #drillLines(index, netName, sourceNetNames) {
|
|
247
|
+
return [
|
|
248
|
+
...CircuitJsonManufacturingBuilder.#all(index, 'pcb_via')
|
|
249
|
+
.filter(
|
|
250
|
+
(via) =>
|
|
251
|
+
CircuitJsonManufacturingBuilder.#netName(
|
|
252
|
+
via,
|
|
253
|
+
sourceNetNames
|
|
254
|
+
) === netName
|
|
255
|
+
)
|
|
256
|
+
.map((via) =>
|
|
257
|
+
CircuitJsonManufacturingBuilder.#drillLine(via, 'via')
|
|
258
|
+
),
|
|
259
|
+
...CircuitJsonManufacturingBuilder.#all(index, 'pcb_plated_hole')
|
|
260
|
+
.filter(
|
|
261
|
+
(hole) =>
|
|
262
|
+
CircuitJsonManufacturingBuilder.#netName(
|
|
263
|
+
hole,
|
|
264
|
+
sourceNetNames
|
|
265
|
+
) === netName
|
|
266
|
+
)
|
|
267
|
+
.map((hole) =>
|
|
268
|
+
CircuitJsonManufacturingBuilder.#drillLine(
|
|
269
|
+
hole,
|
|
270
|
+
'plated_hole'
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
].filter(Boolean)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Builds one drill feature line.
|
|
278
|
+
* @param {object} element Drill-bearing element.
|
|
279
|
+
* @param {'via' | 'plated_hole'} kind Drill line kind.
|
|
280
|
+
* @returns {string}
|
|
281
|
+
*/
|
|
282
|
+
static #drillLine(element, kind) {
|
|
283
|
+
const point = CircuitJsonUnits.optionalPoint(element.center || element)
|
|
284
|
+
const layers = CircuitJsonManufacturingBuilder.#elementLayers(element)
|
|
285
|
+
return (
|
|
286
|
+
' (' +
|
|
287
|
+
kind +
|
|
288
|
+
' ' +
|
|
289
|
+
[
|
|
290
|
+
CircuitJsonManufacturingBuilder.#token(
|
|
291
|
+
element.pcb_via_id ||
|
|
292
|
+
element.pcb_plated_hole_id ||
|
|
293
|
+
element.pcb_hole_id ||
|
|
294
|
+
''
|
|
295
|
+
),
|
|
296
|
+
CircuitJsonManufacturingBuilder.#round(point?.x),
|
|
297
|
+
CircuitJsonManufacturingBuilder.#round(point?.y),
|
|
298
|
+
CircuitJsonManufacturingBuilder.#round(
|
|
299
|
+
CircuitJsonManufacturingBuilder.#holeDiameter(element)
|
|
300
|
+
),
|
|
301
|
+
CircuitJsonManufacturingBuilder.#round(
|
|
302
|
+
CircuitJsonManufacturingBuilder.#outerDiameter(element)
|
|
303
|
+
),
|
|
304
|
+
...layers
|
|
305
|
+
].join(' ') +
|
|
306
|
+
')'
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
222
310
|
/**
|
|
223
311
|
* Builds routed wire lines for one net.
|
|
224
312
|
* @param {{ elementsByType?: Map<string, object[]> }} index Element index.
|
|
225
313
|
* @param {string} netName Net name.
|
|
314
|
+
* @param {Map<string, string>} sourceNetNames Source net lookup.
|
|
226
315
|
* @returns {string[]}
|
|
227
316
|
*/
|
|
228
|
-
static #wireLines(index, netName) {
|
|
317
|
+
static #wireLines(index, netName, sourceNetNames) {
|
|
229
318
|
return CircuitJsonManufacturingBuilder.#all(index, 'pcb_trace')
|
|
230
319
|
.filter(
|
|
231
320
|
(trace) =>
|
|
232
|
-
CircuitJsonManufacturingBuilder.#netName(
|
|
321
|
+
CircuitJsonManufacturingBuilder.#netName(
|
|
322
|
+
trace,
|
|
323
|
+
sourceNetNames
|
|
324
|
+
) === netName
|
|
233
325
|
)
|
|
234
326
|
.flatMap((trace) =>
|
|
235
327
|
CircuitJsonManufacturingBuilder.#traceWireLines(trace)
|
|
@@ -283,25 +375,402 @@ export class CircuitJsonManufacturingBuilder {
|
|
|
283
375
|
*/
|
|
284
376
|
static #netNames(index) {
|
|
285
377
|
const names = new Set()
|
|
286
|
-
|
|
287
|
-
index
|
|
288
|
-
|
|
289
|
-
)) {
|
|
290
|
-
const name = String(net.name || net.source_net_id || '').trim()
|
|
378
|
+
const sourceNetNames =
|
|
379
|
+
CircuitJsonManufacturingBuilder.#sourceNetNames(index)
|
|
380
|
+
for (const name of sourceNetNames.values()) {
|
|
291
381
|
if (name) names.add(name)
|
|
292
382
|
}
|
|
293
|
-
for (const type of [
|
|
383
|
+
for (const type of [
|
|
384
|
+
'pcb_smtpad',
|
|
385
|
+
'pcb_trace',
|
|
386
|
+
'pcb_via',
|
|
387
|
+
'pcb_plated_hole',
|
|
388
|
+
'pcb_trace_hint',
|
|
389
|
+
'pcb_breakout_point'
|
|
390
|
+
]) {
|
|
294
391
|
for (const element of CircuitJsonManufacturingBuilder.#all(
|
|
295
392
|
index,
|
|
296
393
|
type
|
|
297
394
|
)) {
|
|
298
|
-
const name = CircuitJsonManufacturingBuilder.#netName(
|
|
395
|
+
const name = CircuitJsonManufacturingBuilder.#netName(
|
|
396
|
+
element,
|
|
397
|
+
sourceNetNames
|
|
398
|
+
)
|
|
299
399
|
if (name) names.add(name)
|
|
300
400
|
}
|
|
301
401
|
}
|
|
302
402
|
return [...names].sort((left, right) => left.localeCompare(right))
|
|
303
403
|
}
|
|
304
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Builds route guide metadata from hints and breakout points.
|
|
407
|
+
* @param {{ elementsByType?: Map<string, object[]> }} index Element index.
|
|
408
|
+
* @returns {object[]}
|
|
409
|
+
*/
|
|
410
|
+
static #routingGuides(index) {
|
|
411
|
+
const sourceNetNames =
|
|
412
|
+
CircuitJsonManufacturingBuilder.#sourceNetNames(index)
|
|
413
|
+
return [
|
|
414
|
+
...CircuitJsonManufacturingBuilder.#all(index, 'pcb_trace_hint')
|
|
415
|
+
.map((hint) =>
|
|
416
|
+
CircuitJsonManufacturingBuilder.#traceHintGuide(
|
|
417
|
+
hint,
|
|
418
|
+
sourceNetNames
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
.filter(Boolean),
|
|
422
|
+
...CircuitJsonManufacturingBuilder.#all(index, 'pcb_breakout_point')
|
|
423
|
+
.map((point) =>
|
|
424
|
+
CircuitJsonManufacturingBuilder.#breakoutGuide(
|
|
425
|
+
point,
|
|
426
|
+
sourceNetNames
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
.filter(Boolean)
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Builds one trace hint guide.
|
|
435
|
+
* @param {object} hint Trace hint element.
|
|
436
|
+
* @param {Map<string, string>} sourceNetNames Source net lookup.
|
|
437
|
+
* @returns {object | null}
|
|
438
|
+
*/
|
|
439
|
+
static #traceHintGuide(hint, sourceNetNames) {
|
|
440
|
+
const route = (Array.isArray(hint.route) ? hint.route : [])
|
|
441
|
+
.map((point) =>
|
|
442
|
+
CircuitJsonManufacturingBuilder.#routeGuidePoint(point)
|
|
443
|
+
)
|
|
444
|
+
.filter(Boolean)
|
|
445
|
+
if (!route.length) return null
|
|
446
|
+
return {
|
|
447
|
+
type: 'trace_hint',
|
|
448
|
+
id: String(hint.pcb_trace_hint_id || ''),
|
|
449
|
+
pcbComponentId: String(hint.pcb_component_id || ''),
|
|
450
|
+
pcbPortId: String(hint.pcb_port_id || ''),
|
|
451
|
+
sourceNetId: String(hint.source_net_id || ''),
|
|
452
|
+
netName: CircuitJsonManufacturingBuilder.#netName(
|
|
453
|
+
hint,
|
|
454
|
+
sourceNetNames
|
|
455
|
+
),
|
|
456
|
+
subcircuitId: String(hint.subcircuit_id || ''),
|
|
457
|
+
route
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Builds one breakout guide.
|
|
463
|
+
* @param {object} breakout Breakout point element.
|
|
464
|
+
* @param {Map<string, string>} sourceNetNames Source net lookup.
|
|
465
|
+
* @returns {object | null}
|
|
466
|
+
*/
|
|
467
|
+
static #breakoutGuide(breakout, sourceNetNames) {
|
|
468
|
+
const point = CircuitJsonUnits.optionalPoint(
|
|
469
|
+
breakout.center || breakout
|
|
470
|
+
)
|
|
471
|
+
if (!point) return null
|
|
472
|
+
return {
|
|
473
|
+
type: 'breakout_point',
|
|
474
|
+
id: String(breakout.pcb_breakout_point_id || ''),
|
|
475
|
+
pcbGroupId: String(breakout.pcb_group_id || ''),
|
|
476
|
+
sourceTraceId: String(breakout.source_trace_id || ''),
|
|
477
|
+
sourcePortId: String(breakout.source_port_id || ''),
|
|
478
|
+
sourceNetId: String(breakout.source_net_id || ''),
|
|
479
|
+
netName: CircuitJsonManufacturingBuilder.#netName(
|
|
480
|
+
breakout,
|
|
481
|
+
sourceNetNames
|
|
482
|
+
),
|
|
483
|
+
subcircuitId: String(breakout.subcircuit_id || ''),
|
|
484
|
+
point: {
|
|
485
|
+
x: CircuitJsonManufacturingBuilder.#round(point.x),
|
|
486
|
+
y: CircuitJsonManufacturingBuilder.#round(point.y)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Builds a normalized route guide point.
|
|
493
|
+
* @param {object} value Point row.
|
|
494
|
+
* @returns {{ x: number, y: number, layer: string } | null}
|
|
495
|
+
*/
|
|
496
|
+
static #routeGuidePoint(value) {
|
|
497
|
+
const point = CircuitJsonUnits.optionalPoint(value?.center || value)
|
|
498
|
+
if (!point) return null
|
|
499
|
+
return {
|
|
500
|
+
x: CircuitJsonManufacturingBuilder.#round(point.x),
|
|
501
|
+
y: CircuitJsonManufacturingBuilder.#round(point.y),
|
|
502
|
+
layer: CircuitJsonManufacturingBuilder.#layer(value?.layer)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Builds fabrication note metadata.
|
|
508
|
+
* @param {{ elementsByType?: Map<string, object[]> }} index Element index.
|
|
509
|
+
* @returns {object[]}
|
|
510
|
+
*/
|
|
511
|
+
static #fabricationNotes(index) {
|
|
512
|
+
return [
|
|
513
|
+
...CircuitJsonManufacturingBuilder.#all(
|
|
514
|
+
index,
|
|
515
|
+
'pcb_fabrication_note_text'
|
|
516
|
+
).map((note) =>
|
|
517
|
+
CircuitJsonManufacturingBuilder.#fabricationTextNote(note)
|
|
518
|
+
),
|
|
519
|
+
...CircuitJsonManufacturingBuilder.#all(
|
|
520
|
+
index,
|
|
521
|
+
'pcb_fabrication_note_path'
|
|
522
|
+
).map((note) =>
|
|
523
|
+
CircuitJsonManufacturingBuilder.#fabricationPathNote(note)
|
|
524
|
+
),
|
|
525
|
+
...CircuitJsonManufacturingBuilder.#all(
|
|
526
|
+
index,
|
|
527
|
+
'pcb_fabrication_note_rect'
|
|
528
|
+
).map((note) =>
|
|
529
|
+
CircuitJsonManufacturingBuilder.#fabricationRectNote(note)
|
|
530
|
+
),
|
|
531
|
+
...CircuitJsonManufacturingBuilder.#all(
|
|
532
|
+
index,
|
|
533
|
+
'pcb_fabrication_note_dimension'
|
|
534
|
+
).map((note) =>
|
|
535
|
+
CircuitJsonManufacturingBuilder.#fabricationDimensionNote(note)
|
|
536
|
+
)
|
|
537
|
+
].filter(Boolean)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Builds one fabrication text note row.
|
|
542
|
+
* @param {object} note Fabrication text element.
|
|
543
|
+
* @returns {object | null}
|
|
544
|
+
*/
|
|
545
|
+
static #fabricationTextNote(note) {
|
|
546
|
+
const anchor = CircuitJsonUnits.optionalPoint(
|
|
547
|
+
note.anchor_position || note.center || note
|
|
548
|
+
) || { x: 0, y: 0 }
|
|
549
|
+
return {
|
|
550
|
+
...CircuitJsonManufacturingBuilder.#fabricationBaseNote(
|
|
551
|
+
note,
|
|
552
|
+
'text'
|
|
553
|
+
),
|
|
554
|
+
text: String(note.text || ''),
|
|
555
|
+
anchor: {
|
|
556
|
+
x: CircuitJsonManufacturingBuilder.#round(anchor.x),
|
|
557
|
+
y: CircuitJsonManufacturingBuilder.#round(anchor.y)
|
|
558
|
+
},
|
|
559
|
+
rotation: CircuitJsonUnits.angle(note.ccw_rotation, 0),
|
|
560
|
+
fontSize: CircuitJsonUnits.length(note.font_size, 1),
|
|
561
|
+
color: String(note.color || '')
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Builds one fabrication path note row.
|
|
567
|
+
* @param {object} note Fabrication path element.
|
|
568
|
+
* @returns {object | null}
|
|
569
|
+
*/
|
|
570
|
+
static #fabricationPathNote(note) {
|
|
571
|
+
const route = (Array.isArray(note.route) ? note.route : [])
|
|
572
|
+
.map((point) => CircuitJsonUnits.optionalPoint(point))
|
|
573
|
+
.filter(Boolean)
|
|
574
|
+
.map((point) => ({
|
|
575
|
+
x: CircuitJsonManufacturingBuilder.#round(point.x),
|
|
576
|
+
y: CircuitJsonManufacturingBuilder.#round(point.y)
|
|
577
|
+
}))
|
|
578
|
+
if (!route.length) return null
|
|
579
|
+
return {
|
|
580
|
+
...CircuitJsonManufacturingBuilder.#fabricationBaseNote(
|
|
581
|
+
note,
|
|
582
|
+
'path'
|
|
583
|
+
),
|
|
584
|
+
route,
|
|
585
|
+
strokeWidth: CircuitJsonUnits.length(note.stroke_width, 0),
|
|
586
|
+
color: String(note.color || '')
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Builds one fabrication rectangle note row.
|
|
592
|
+
* @param {object} note Fabrication rectangle element.
|
|
593
|
+
* @returns {object | null}
|
|
594
|
+
*/
|
|
595
|
+
static #fabricationRectNote(note) {
|
|
596
|
+
const center = CircuitJsonUnits.optionalPoint(note.center || note)
|
|
597
|
+
if (!center) return null
|
|
598
|
+
return {
|
|
599
|
+
...CircuitJsonManufacturingBuilder.#fabricationBaseNote(
|
|
600
|
+
note,
|
|
601
|
+
'rect'
|
|
602
|
+
),
|
|
603
|
+
center: {
|
|
604
|
+
x: CircuitJsonManufacturingBuilder.#round(center.x),
|
|
605
|
+
y: CircuitJsonManufacturingBuilder.#round(center.y)
|
|
606
|
+
},
|
|
607
|
+
width: CircuitJsonUnits.length(note.width, 0),
|
|
608
|
+
height: CircuitJsonUnits.length(note.height, 0),
|
|
609
|
+
strokeWidth: CircuitJsonUnits.length(note.stroke_width, 0.1),
|
|
610
|
+
cornerRadius: CircuitJsonUnits.length(note.corner_radius, 0),
|
|
611
|
+
isFilled: note.is_filled === true,
|
|
612
|
+
hasStroke: note.has_stroke !== false,
|
|
613
|
+
isStrokeDashed: note.is_stroke_dashed === true,
|
|
614
|
+
color: String(note.color || '')
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Builds one fabrication dimension note row.
|
|
620
|
+
* @param {object} note Fabrication dimension element.
|
|
621
|
+
* @returns {object | null}
|
|
622
|
+
*/
|
|
623
|
+
static #fabricationDimensionNote(note) {
|
|
624
|
+
const from = CircuitJsonUnits.optionalPoint(note.from)
|
|
625
|
+
const to = CircuitJsonUnits.optionalPoint(note.to)
|
|
626
|
+
if (!from || !to) return null
|
|
627
|
+
return {
|
|
628
|
+
...CircuitJsonManufacturingBuilder.#fabricationBaseNote(
|
|
629
|
+
note,
|
|
630
|
+
'dimension'
|
|
631
|
+
),
|
|
632
|
+
from: {
|
|
633
|
+
x: CircuitJsonManufacturingBuilder.#round(from.x),
|
|
634
|
+
y: CircuitJsonManufacturingBuilder.#round(from.y)
|
|
635
|
+
},
|
|
636
|
+
to: {
|
|
637
|
+
x: CircuitJsonManufacturingBuilder.#round(to.x),
|
|
638
|
+
y: CircuitJsonManufacturingBuilder.#round(to.y)
|
|
639
|
+
},
|
|
640
|
+
text: String(note.text || ''),
|
|
641
|
+
offset: CircuitJsonUnits.length(note.offset, 0),
|
|
642
|
+
offsetDistance: CircuitJsonUnits.length(note.offset_distance, 0),
|
|
643
|
+
offsetDirection: CircuitJsonManufacturingBuilder.#offsetDirection(
|
|
644
|
+
note.offset_direction
|
|
645
|
+
),
|
|
646
|
+
rotation: CircuitJsonUnits.angle(
|
|
647
|
+
note.text_ccw_rotation ?? note.ccw_rotation,
|
|
648
|
+
0
|
|
649
|
+
),
|
|
650
|
+
fontSize: CircuitJsonUnits.length(note.font_size, 1),
|
|
651
|
+
arrowSize: CircuitJsonUnits.length(note.arrow_size, 1),
|
|
652
|
+
color: String(note.color || '')
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Builds common fabrication note metadata.
|
|
658
|
+
* @param {object} note Fabrication note element.
|
|
659
|
+
* @param {string} type Note type.
|
|
660
|
+
* @returns {object}
|
|
661
|
+
*/
|
|
662
|
+
static #fabricationBaseNote(note, type) {
|
|
663
|
+
return {
|
|
664
|
+
type,
|
|
665
|
+
elementType: String(note.type || ''),
|
|
666
|
+
id: CircuitJsonIndexer.getElementId(note),
|
|
667
|
+
pcbComponentId: String(note.pcb_component_id || ''),
|
|
668
|
+
pcbGroupId: String(note.pcb_group_id || ''),
|
|
669
|
+
subcircuitId: String(note.subcircuit_id || ''),
|
|
670
|
+
layer: CircuitJsonManufacturingBuilder.#layer(note.layer)
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Normalizes an offset direction row.
|
|
676
|
+
* @param {object | null | undefined} direction Direction row.
|
|
677
|
+
* @returns {{ x: number, y: number } | null}
|
|
678
|
+
*/
|
|
679
|
+
static #offsetDirection(direction) {
|
|
680
|
+
const x = Number(direction?.x)
|
|
681
|
+
const y = Number(direction?.y)
|
|
682
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null
|
|
683
|
+
return {
|
|
684
|
+
x: CircuitJsonManufacturingBuilder.#round(x),
|
|
685
|
+
y: CircuitJsonManufacturingBuilder.#round(y)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Builds source net id to display name lookup.
|
|
691
|
+
* @param {{ elementsByType?: Map<string, object[]> }} index Element index.
|
|
692
|
+
* @returns {Map<string, string>}
|
|
693
|
+
*/
|
|
694
|
+
static #sourceNetNames(index) {
|
|
695
|
+
return new Map(
|
|
696
|
+
CircuitJsonManufacturingBuilder.#all(index, 'source_net').map(
|
|
697
|
+
(net) => {
|
|
698
|
+
const id = String(net.source_net_id || '').trim()
|
|
699
|
+
const name = String(net.name || id).trim()
|
|
700
|
+
return [id, name]
|
|
701
|
+
}
|
|
702
|
+
)
|
|
703
|
+
)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Resolves layer names from a drill-bearing element.
|
|
708
|
+
* @param {object} element Element row.
|
|
709
|
+
* @returns {string[]}
|
|
710
|
+
*/
|
|
711
|
+
static #elementLayers(element) {
|
|
712
|
+
const values = Array.isArray(element.layers)
|
|
713
|
+
? element.layers
|
|
714
|
+
: [element.from_layer, element.to_layer, element.layer]
|
|
715
|
+
const layers = values
|
|
716
|
+
.map((value) => CircuitJsonManufacturingBuilder.#layer(value))
|
|
717
|
+
.filter(Boolean)
|
|
718
|
+
return CircuitJsonManufacturingBuilder.#uniqueStrings(
|
|
719
|
+
layers.length ? layers : ['top', 'bottom']
|
|
720
|
+
)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Resolves drill hole diameter.
|
|
725
|
+
* @param {object} element Element row.
|
|
726
|
+
* @returns {number}
|
|
727
|
+
*/
|
|
728
|
+
static #holeDiameter(element) {
|
|
729
|
+
return CircuitJsonManufacturingBuilder.#maxLength([
|
|
730
|
+
element.hole_diameter,
|
|
731
|
+
element.hole_width,
|
|
732
|
+
element.hole_height
|
|
733
|
+
])
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Resolves drill outer diameter.
|
|
738
|
+
* @param {object} element Element row.
|
|
739
|
+
* @returns {number}
|
|
740
|
+
*/
|
|
741
|
+
static #outerDiameter(element) {
|
|
742
|
+
return CircuitJsonManufacturingBuilder.#maxLength([
|
|
743
|
+
element.outer_diameter,
|
|
744
|
+
element.outer_width,
|
|
745
|
+
element.outer_height,
|
|
746
|
+
element.rect_pad_width,
|
|
747
|
+
element.rect_pad_height
|
|
748
|
+
])
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Returns the largest valid length in a list.
|
|
753
|
+
* @param {unknown[]} values Candidate values.
|
|
754
|
+
* @returns {number}
|
|
755
|
+
*/
|
|
756
|
+
static #maxLength(values) {
|
|
757
|
+
const lengths = values
|
|
758
|
+
.map((value) => CircuitJsonUnits.optionalLength(value))
|
|
759
|
+
.filter((value) => value !== null)
|
|
760
|
+
return lengths.length ? Math.max(...lengths) : 0
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Returns unique strings in input order.
|
|
765
|
+
* @param {string[]} values Candidate strings.
|
|
766
|
+
* @returns {string[]}
|
|
767
|
+
*/
|
|
768
|
+
static #uniqueStrings(values) {
|
|
769
|
+
return [
|
|
770
|
+
...new Set(values.map((value) => String(value || '').trim()))
|
|
771
|
+
].filter(Boolean)
|
|
772
|
+
}
|
|
773
|
+
|
|
305
774
|
/**
|
|
306
775
|
* Resolves layer names from board metadata.
|
|
307
776
|
* @param {object | undefined} board Board element.
|
|
@@ -365,14 +834,17 @@ export class CircuitJsonManufacturingBuilder {
|
|
|
365
834
|
/**
|
|
366
835
|
* Resolves a normalized net name.
|
|
367
836
|
* @param {object} element Element row.
|
|
837
|
+
* @param {Map<string, string>} [sourceNetNames] Source net lookup.
|
|
368
838
|
* @returns {string}
|
|
369
839
|
*/
|
|
370
|
-
static #netName(element) {
|
|
840
|
+
static #netName(element, sourceNetNames = new Map()) {
|
|
841
|
+
const sourceNetId = String(element?.source_net_id || '').trim()
|
|
371
842
|
return String(
|
|
372
843
|
element?.netName ??
|
|
373
844
|
element?.net ??
|
|
374
845
|
element?.net_name ??
|
|
375
846
|
element?.source_net_name ??
|
|
847
|
+
sourceNetNames.get(sourceNetId) ??
|
|
376
848
|
''
|
|
377
849
|
).trim()
|
|
378
850
|
}
|