@tscircuit/copper-pour-solver 0.0.34 → 0.0.36
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/README.md +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +165 -25
- package/lib/circuit-json/ConvertCircuitJsonToInputProblemOptions.ts +1 -0
- package/lib/circuit-json/buildSubcircuitConnectivityLookup.ts +199 -16
- package/lib/circuit-json/convert-circuit-json-to-input-problem.ts +9 -4
- package/lib/circuit-json/resolvePourConnectivityKey.ts +34 -7
- package/package.json +5 -4
- package/tests/__snapshots__/tsx-subcircuit-connectivity01.snap.svg +1 -0
- package/tests/__snapshots__/tsx-subcircuit-connectivity02.snap.svg +1 -0
- package/tests/assets/subcircuit-connectivity-scope.json +126 -0
- package/tests/connectivity-key-api.test.ts +46 -0
- package/tests/tsx-subcircuit-connectivity.test.tsx +223 -0
package/README.md
CHANGED
|
@@ -67,12 +67,21 @@ const gnd = circuitJson.find(
|
|
|
67
67
|
|
|
68
68
|
const inputProblem = convertCircuitJsonToInputProblem(circuitJson, {
|
|
69
69
|
layer: "top",
|
|
70
|
+
subcircuit_id: gnd.subcircuit_id,
|
|
70
71
|
subcircuit_connectivity_map_key: gnd.subcircuit_connectivity_map_key,
|
|
71
72
|
pad_margin: 0.4,
|
|
72
73
|
trace_margin: 0.2,
|
|
73
74
|
})
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
Pass `subcircuit_id` when selecting a net inside a subcircuit. The converter
|
|
78
|
+
considers that subcircuit and its child subcircuits, but it does not treat
|
|
79
|
+
matching child `subcircuit_connectivity_map_key` values as connected unless the
|
|
80
|
+
Circuit JSON connectivity actually connects them. Internally, the generated
|
|
81
|
+
`globalConnectivityMap` is kept separate from the scoped
|
|
82
|
+
`subcircuitConnectivityMap`; scoped solver connectivity keys are prefixed with
|
|
83
|
+
their subcircuit id.
|
|
84
|
+
|
|
76
85
|
Do not generate or pass ids from `circuit-json-to-connectivity-map`. The
|
|
77
86
|
converter handles PCB connectivity internally and normalizes it to stable
|
|
78
87
|
`subcircuit_connectivity_map_key` values.
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ declare const initializeManifoldGeometry: () => Promise<void>;
|
|
|
67
67
|
|
|
68
68
|
interface ConvertCircuitJsonToInputProblemOptions {
|
|
69
69
|
layer: LayerRef;
|
|
70
|
+
subcircuit_id?: string;
|
|
70
71
|
source_net_id?: string;
|
|
71
72
|
source_net_name?: string;
|
|
72
73
|
subcircuit_connectivity_map_key?: string;
|
package/dist/index.js
CHANGED
|
@@ -548,50 +548,170 @@ var getElementSubcircuitConnectivityKey = (element) => {
|
|
|
548
548
|
};
|
|
549
549
|
|
|
550
550
|
// lib/circuit-json/buildSubcircuitConnectivityLookup.ts
|
|
551
|
-
var
|
|
551
|
+
var getElementSubcircuitId = (element) => {
|
|
552
|
+
const subcircuitId = element.subcircuit_id;
|
|
553
|
+
return typeof subcircuitId === "string" && subcircuitId.length > 0 ? subcircuitId : void 0;
|
|
554
|
+
};
|
|
555
|
+
var getScopedSubcircuitConnectivityKey = (subcircuitId, subcircuitConnectivityMapKey) => subcircuitId ? `subcircuit:${subcircuitId}:connectivity:${subcircuitConnectivityMapKey}` : subcircuitConnectivityMapKey;
|
|
556
|
+
var getDescendantSubcircuitIds = (circuitJson, rootSubcircuitId) => {
|
|
557
|
+
if (!rootSubcircuitId) return void 0;
|
|
558
|
+
const sourceGroupIdToSubcircuitId = {};
|
|
559
|
+
for (const element of circuitJson) {
|
|
560
|
+
if (element.type !== "source_group") continue;
|
|
561
|
+
const sourceGroupId = element.source_group_id;
|
|
562
|
+
const subcircuitId = getElementSubcircuitId(element);
|
|
563
|
+
if (typeof sourceGroupId === "string" && subcircuitId) {
|
|
564
|
+
sourceGroupIdToSubcircuitId[sourceGroupId] = subcircuitId;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const descendantSubcircuitIds = /* @__PURE__ */ new Set([rootSubcircuitId]);
|
|
568
|
+
let changed = true;
|
|
569
|
+
while (changed) {
|
|
570
|
+
changed = false;
|
|
571
|
+
for (const element of circuitJson) {
|
|
572
|
+
if (element.type !== "source_group") continue;
|
|
573
|
+
const subcircuitId = getElementSubcircuitId(element);
|
|
574
|
+
if (!subcircuitId || descendantSubcircuitIds.has(subcircuitId)) continue;
|
|
575
|
+
const parentSubcircuitId = element.parent_subcircuit_id;
|
|
576
|
+
const parentSourceGroupId = element.parent_source_group_id;
|
|
577
|
+
const parentSourceGroupSubcircuitId = typeof parentSourceGroupId === "string" ? sourceGroupIdToSubcircuitId[parentSourceGroupId] : void 0;
|
|
578
|
+
if (typeof parentSubcircuitId === "string" && descendantSubcircuitIds.has(parentSubcircuitId) || parentSourceGroupSubcircuitId && descendantSubcircuitIds.has(parentSourceGroupSubcircuitId)) {
|
|
579
|
+
descendantSubcircuitIds.add(subcircuitId);
|
|
580
|
+
changed = true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return descendantSubcircuitIds;
|
|
585
|
+
};
|
|
586
|
+
var buildSubcircuitConnectivityLookup = (circuitJson, globalConnectivityMap, rootSubcircuitId) => {
|
|
587
|
+
const descendantSubcircuitIds = getDescendantSubcircuitIds(
|
|
588
|
+
circuitJson,
|
|
589
|
+
rootSubcircuitId
|
|
590
|
+
);
|
|
552
591
|
const idToSubcircuitConnectivityKey = {};
|
|
592
|
+
const idToSubcircuitId = {};
|
|
593
|
+
const scopedKeyToIds = {};
|
|
594
|
+
const localSubcircuitConnectivityKeys = /* @__PURE__ */ new Set();
|
|
553
595
|
for (const element of circuitJson) {
|
|
554
596
|
const id = getElementId(element);
|
|
555
597
|
const key = getElementSubcircuitConnectivityKey(element);
|
|
598
|
+
const subcircuitId = getElementSubcircuitId(element);
|
|
599
|
+
if (id) {
|
|
600
|
+
idToSubcircuitId[id] = subcircuitId;
|
|
601
|
+
}
|
|
602
|
+
if (descendantSubcircuitIds && (!subcircuitId || !descendantSubcircuitIds.has(subcircuitId))) {
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
556
605
|
if (id && key) {
|
|
557
|
-
|
|
606
|
+
const scopedKey = getScopedSubcircuitConnectivityKey(subcircuitId, key);
|
|
607
|
+
idToSubcircuitConnectivityKey[id] = scopedKey;
|
|
608
|
+
localSubcircuitConnectivityKeys.add(key);
|
|
609
|
+
scopedKeyToIds[scopedKey] ??= [];
|
|
610
|
+
scopedKeyToIds[scopedKey].push(id);
|
|
558
611
|
}
|
|
559
612
|
}
|
|
560
613
|
const generatedNetIdToSubcircuitConnectivityKey = {};
|
|
561
614
|
for (const [generatedNetId, connectedIds] of Object.entries(
|
|
562
|
-
|
|
615
|
+
globalConnectivityMap.netMap
|
|
563
616
|
)) {
|
|
564
617
|
const connectedSubcircuitKeys = new Set(
|
|
565
618
|
connectedIds.map((id) => idToSubcircuitConnectivityKey[id]).filter((key) => Boolean(key))
|
|
566
619
|
);
|
|
567
|
-
|
|
620
|
+
const subcircuitKey = Array.from(connectedSubcircuitKeys).sort()[0];
|
|
621
|
+
if (subcircuitKey) {
|
|
622
|
+
generatedNetIdToSubcircuitConnectivityKey[generatedNetId] = subcircuitKey;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const subcircuitConnectivityMap = {};
|
|
626
|
+
for (const [scopedKey, ids] of Object.entries(scopedKeyToIds)) {
|
|
627
|
+
const resolvedKeys = /* @__PURE__ */ new Set();
|
|
628
|
+
for (const id of ids) {
|
|
629
|
+
const generatedNetId = globalConnectivityMap.getNetConnectedToId(id);
|
|
630
|
+
const resolvedKey2 = generatedNetId ? generatedNetIdToSubcircuitConnectivityKey[generatedNetId] : void 0;
|
|
631
|
+
resolvedKeys.add(resolvedKey2 ?? scopedKey);
|
|
632
|
+
}
|
|
633
|
+
if (resolvedKeys.size > 1) {
|
|
568
634
|
throw new Error(
|
|
569
|
-
`
|
|
635
|
+
`subcircuit_connectivity_map_key "${scopedKey}" maps to multiple global connectivity keys: ${Array.from(resolvedKeys).join(", ")}`
|
|
570
636
|
);
|
|
571
637
|
}
|
|
572
|
-
const
|
|
573
|
-
if (
|
|
574
|
-
|
|
638
|
+
const resolvedKey = resolvedKeys.values().next().value;
|
|
639
|
+
if (resolvedKey) {
|
|
640
|
+
subcircuitConnectivityMap[scopedKey] = resolvedKey;
|
|
575
641
|
}
|
|
576
642
|
}
|
|
577
643
|
return {
|
|
578
|
-
knownSubcircuitConnectivityKeys: new Set(
|
|
579
|
-
Object.
|
|
580
|
-
|
|
644
|
+
knownSubcircuitConnectivityKeys: /* @__PURE__ */ new Set([
|
|
645
|
+
...Object.keys(scopedKeyToIds),
|
|
646
|
+
...localSubcircuitConnectivityKeys
|
|
647
|
+
]),
|
|
648
|
+
descendantSubcircuitIds,
|
|
649
|
+
getScopedSubcircuitConnectivityKey,
|
|
650
|
+
getElementSubcircuitId,
|
|
651
|
+
resolveSubcircuitConnectivityKey(subcircuitConnectivityMapKey, subcircuitId) {
|
|
652
|
+
const matchingScopedKeys = Object.keys(scopedKeyToIds).filter(
|
|
653
|
+
(scopedKey) => scopedKey === getScopedSubcircuitConnectivityKey(
|
|
654
|
+
subcircuitId,
|
|
655
|
+
subcircuitConnectivityMapKey
|
|
656
|
+
)
|
|
657
|
+
);
|
|
658
|
+
if (subcircuitId && matchingScopedKeys.length === 0 && descendantSubcircuitIds) {
|
|
659
|
+
matchingScopedKeys.push(
|
|
660
|
+
...Object.keys(scopedKeyToIds).filter(
|
|
661
|
+
(scopedKey) => scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`)
|
|
662
|
+
)
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
if (!subcircuitId) {
|
|
666
|
+
matchingScopedKeys.push(
|
|
667
|
+
...Object.keys(scopedKeyToIds).filter(
|
|
668
|
+
(scopedKey) => scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`)
|
|
669
|
+
)
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const uniqueMatchingScopedKeys = Array.from(new Set(matchingScopedKeys));
|
|
673
|
+
if (uniqueMatchingScopedKeys.length === 0) {
|
|
674
|
+
if (subcircuitId && descendantSubcircuitIds) {
|
|
675
|
+
throw new Error(
|
|
676
|
+
`No subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" found in subcircuit "${subcircuitId}" or its child subcircuits.`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
return getScopedSubcircuitConnectivityKey(
|
|
680
|
+
subcircuitId,
|
|
681
|
+
subcircuitConnectivityMapKey
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
if (uniqueMatchingScopedKeys.length > 1) {
|
|
685
|
+
throw new Error(
|
|
686
|
+
`subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" exists in multiple subcircuits. Pass subcircuit_id to disambiguate.`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
return subcircuitConnectivityMap[uniqueMatchingScopedKeys[0]] ?? uniqueMatchingScopedKeys[0];
|
|
690
|
+
},
|
|
581
691
|
getSubcircuitConnectivityKeyForId(id) {
|
|
692
|
+
if (descendantSubcircuitIds) {
|
|
693
|
+
const subcircuitId = idToSubcircuitId[id];
|
|
694
|
+
if (!subcircuitId || !descendantSubcircuitIds.has(subcircuitId)) {
|
|
695
|
+
return void 0;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
582
698
|
const directKey = idToSubcircuitConnectivityKey[id];
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
699
|
+
const generatedNetId = globalConnectivityMap.getNetConnectedToId(id);
|
|
700
|
+
if (!generatedNetId) {
|
|
701
|
+
return directKey ? subcircuitConnectivityMap[directKey] ?? directKey : void 0;
|
|
702
|
+
}
|
|
703
|
+
return generatedNetIdToSubcircuitConnectivityKey[generatedNetId] ?? (directKey ? subcircuitConnectivityMap[directKey] : void 0) ?? directKey;
|
|
587
704
|
}
|
|
588
705
|
};
|
|
589
706
|
};
|
|
590
707
|
|
|
591
708
|
// lib/circuit-json/resolvePourConnectivityKey.ts
|
|
592
|
-
var resolvePourConnectivityKey = (circuitJson, options,
|
|
709
|
+
var resolvePourConnectivityKey = (circuitJson, options, subcircuitConnectivityMap) => {
|
|
593
710
|
if (options.subcircuit_connectivity_map_key) {
|
|
594
|
-
return
|
|
711
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
712
|
+
options.subcircuit_connectivity_map_key,
|
|
713
|
+
options.subcircuit_id
|
|
714
|
+
);
|
|
595
715
|
}
|
|
596
716
|
if (options.source_net_id) {
|
|
597
717
|
const sourceNet = circuitJson.find(
|
|
@@ -605,11 +725,18 @@ var resolvePourConnectivityKey = (circuitJson, options, knownSubcircuitConnectiv
|
|
|
605
725
|
`source_net "${options.source_net_id}" has no subcircuit_connectivity_map_key`
|
|
606
726
|
);
|
|
607
727
|
}
|
|
608
|
-
return
|
|
728
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
729
|
+
sourceNet.subcircuit_connectivity_map_key,
|
|
730
|
+
sourceNet.subcircuit_id ?? options.subcircuit_id
|
|
731
|
+
);
|
|
609
732
|
}
|
|
610
733
|
if (options.source_net_name) {
|
|
611
734
|
const sourceNet = circuitJson.find(
|
|
612
|
-
(element) => element.type === "source_net" && element.name === options.source_net_name
|
|
735
|
+
(element) => element.type === "source_net" && element.name === options.source_net_name && (!options.subcircuit_id || Boolean(
|
|
736
|
+
subcircuitConnectivityMap.descendantSubcircuitIds?.has(
|
|
737
|
+
element.subcircuit_id ?? ""
|
|
738
|
+
)
|
|
739
|
+
))
|
|
613
740
|
);
|
|
614
741
|
if (!sourceNet) {
|
|
615
742
|
throw new Error(
|
|
@@ -621,15 +748,23 @@ var resolvePourConnectivityKey = (circuitJson, options, knownSubcircuitConnectiv
|
|
|
621
748
|
`source_net "${options.source_net_name}" has no subcircuit_connectivity_map_key`
|
|
622
749
|
);
|
|
623
750
|
}
|
|
624
|
-
return
|
|
751
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
752
|
+
sourceNet.subcircuit_connectivity_map_key,
|
|
753
|
+
sourceNet.subcircuit_id ?? options.subcircuit_id
|
|
754
|
+
);
|
|
625
755
|
}
|
|
626
756
|
if (options.pour_connectivity_key) {
|
|
627
|
-
if (!knownSubcircuitConnectivityKeys.has(
|
|
757
|
+
if (!subcircuitConnectivityMap.knownSubcircuitConnectivityKeys.has(
|
|
758
|
+
options.pour_connectivity_key
|
|
759
|
+
)) {
|
|
628
760
|
throw new Error(
|
|
629
761
|
`pour_connectivity_key must be a subcircuit_connectivity_map_key. Use subcircuit_connectivity_map_key, source_net_id, or source_net_name instead of a generated connectivity-map id.`
|
|
630
762
|
);
|
|
631
763
|
}
|
|
632
|
-
return
|
|
764
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
765
|
+
options.pour_connectivity_key,
|
|
766
|
+
options.subcircuit_id
|
|
767
|
+
);
|
|
633
768
|
}
|
|
634
769
|
throw new Error(
|
|
635
770
|
"Copper pour requires source_net_id, source_net_name, or subcircuit_connectivity_map_key"
|
|
@@ -640,13 +775,18 @@ var resolvePourConnectivityKey = (circuitJson, options, knownSubcircuitConnectiv
|
|
|
640
775
|
var convertCircuitJsonToInputProblem = (circuitJson, options) => {
|
|
641
776
|
const pcb_board = circuitJson.find((e) => e.type === "pcb_board");
|
|
642
777
|
if (!pcb_board) throw new Error("No pcb_board found in circuit json");
|
|
643
|
-
const
|
|
644
|
-
const
|
|
778
|
+
const globalConnectivityMap = getFullConnectivityMapFromCircuitJson(circuitJson);
|
|
779
|
+
const subcircuitConnectivityMap = buildSubcircuitConnectivityLookup(
|
|
780
|
+
circuitJson,
|
|
781
|
+
globalConnectivityMap,
|
|
782
|
+
options.subcircuit_id
|
|
783
|
+
);
|
|
645
784
|
const pourConnectivityKey = resolvePourConnectivityKey(
|
|
646
785
|
circuitJson,
|
|
647
786
|
options,
|
|
648
|
-
|
|
787
|
+
subcircuitConnectivityMap
|
|
649
788
|
);
|
|
789
|
+
const { getSubcircuitConnectivityKeyForId } = subcircuitConnectivityMap;
|
|
650
790
|
const pads = [];
|
|
651
791
|
for (const elm of circuitJson) {
|
|
652
792
|
if (elm.type === "pcb_smtpad") {
|
|
@@ -3,52 +3,235 @@ import type { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-conn
|
|
|
3
3
|
import { getElementId } from "@tscircuit/circuit-json-util"
|
|
4
4
|
import { getElementSubcircuitConnectivityKey } from "./getElementSubcircuitConnectivityKey"
|
|
5
5
|
|
|
6
|
+
const getElementSubcircuitId = (
|
|
7
|
+
element: AnyCircuitElement,
|
|
8
|
+
): string | undefined => {
|
|
9
|
+
const subcircuitId = (element as any).subcircuit_id
|
|
10
|
+
return typeof subcircuitId === "string" && subcircuitId.length > 0
|
|
11
|
+
? subcircuitId
|
|
12
|
+
: undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getScopedSubcircuitConnectivityKey = (
|
|
16
|
+
subcircuitId: string | undefined,
|
|
17
|
+
subcircuitConnectivityMapKey: string,
|
|
18
|
+
): string =>
|
|
19
|
+
subcircuitId
|
|
20
|
+
? `subcircuit:${subcircuitId}:connectivity:${subcircuitConnectivityMapKey}`
|
|
21
|
+
: subcircuitConnectivityMapKey
|
|
22
|
+
|
|
23
|
+
const getDescendantSubcircuitIds = (
|
|
24
|
+
circuitJson: AnyCircuitElement[],
|
|
25
|
+
rootSubcircuitId: string | undefined,
|
|
26
|
+
): Set<string> | undefined => {
|
|
27
|
+
if (!rootSubcircuitId) return undefined
|
|
28
|
+
|
|
29
|
+
const sourceGroupIdToSubcircuitId: Record<string, string> = {}
|
|
30
|
+
for (const element of circuitJson) {
|
|
31
|
+
if (element.type !== "source_group") continue
|
|
32
|
+
|
|
33
|
+
const sourceGroupId = (element as any).source_group_id
|
|
34
|
+
const subcircuitId = getElementSubcircuitId(element)
|
|
35
|
+
if (typeof sourceGroupId === "string" && subcircuitId) {
|
|
36
|
+
sourceGroupIdToSubcircuitId[sourceGroupId] = subcircuitId
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const descendantSubcircuitIds = new Set([rootSubcircuitId])
|
|
41
|
+
let changed = true
|
|
42
|
+
while (changed) {
|
|
43
|
+
changed = false
|
|
44
|
+
for (const element of circuitJson) {
|
|
45
|
+
if (element.type !== "source_group") continue
|
|
46
|
+
|
|
47
|
+
const subcircuitId = getElementSubcircuitId(element)
|
|
48
|
+
if (!subcircuitId || descendantSubcircuitIds.has(subcircuitId)) continue
|
|
49
|
+
|
|
50
|
+
const parentSubcircuitId = (element as any).parent_subcircuit_id
|
|
51
|
+
const parentSourceGroupId = (element as any).parent_source_group_id
|
|
52
|
+
const parentSourceGroupSubcircuitId =
|
|
53
|
+
typeof parentSourceGroupId === "string"
|
|
54
|
+
? sourceGroupIdToSubcircuitId[parentSourceGroupId]
|
|
55
|
+
: undefined
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
(typeof parentSubcircuitId === "string" &&
|
|
59
|
+
descendantSubcircuitIds.has(parentSubcircuitId)) ||
|
|
60
|
+
(parentSourceGroupSubcircuitId &&
|
|
61
|
+
descendantSubcircuitIds.has(parentSourceGroupSubcircuitId))
|
|
62
|
+
) {
|
|
63
|
+
descendantSubcircuitIds.add(subcircuitId)
|
|
64
|
+
changed = true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return descendantSubcircuitIds
|
|
70
|
+
}
|
|
71
|
+
|
|
6
72
|
export const buildSubcircuitConnectivityLookup = (
|
|
7
73
|
circuitJson: AnyCircuitElement[],
|
|
8
|
-
|
|
74
|
+
globalConnectivityMap: ReturnType<
|
|
75
|
+
typeof getFullConnectivityMapFromCircuitJson
|
|
76
|
+
>,
|
|
77
|
+
rootSubcircuitId?: string,
|
|
9
78
|
) => {
|
|
79
|
+
const descendantSubcircuitIds = getDescendantSubcircuitIds(
|
|
80
|
+
circuitJson,
|
|
81
|
+
rootSubcircuitId,
|
|
82
|
+
)
|
|
10
83
|
const idToSubcircuitConnectivityKey: Record<string, string> = {}
|
|
84
|
+
const idToSubcircuitId: Record<string, string | undefined> = {}
|
|
85
|
+
const scopedKeyToIds: Record<string, string[]> = {}
|
|
86
|
+
const localSubcircuitConnectivityKeys = new Set<string>()
|
|
11
87
|
|
|
12
88
|
for (const element of circuitJson) {
|
|
13
89
|
const id = getElementId(element)
|
|
14
90
|
const key = getElementSubcircuitConnectivityKey(element)
|
|
91
|
+
const subcircuitId = getElementSubcircuitId(element)
|
|
92
|
+
if (id) {
|
|
93
|
+
idToSubcircuitId[id] = subcircuitId
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
descendantSubcircuitIds &&
|
|
97
|
+
(!subcircuitId || !descendantSubcircuitIds.has(subcircuitId))
|
|
98
|
+
) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
15
102
|
if (id && key) {
|
|
16
|
-
|
|
103
|
+
const scopedKey = getScopedSubcircuitConnectivityKey(subcircuitId, key)
|
|
104
|
+
idToSubcircuitConnectivityKey[id] = scopedKey
|
|
105
|
+
localSubcircuitConnectivityKeys.add(key)
|
|
106
|
+
scopedKeyToIds[scopedKey] ??= []
|
|
107
|
+
scopedKeyToIds[scopedKey].push(id)
|
|
17
108
|
}
|
|
18
109
|
}
|
|
19
110
|
|
|
20
111
|
const generatedNetIdToSubcircuitConnectivityKey: Record<string, string> = {}
|
|
21
112
|
for (const [generatedNetId, connectedIds] of Object.entries(
|
|
22
|
-
|
|
113
|
+
globalConnectivityMap.netMap,
|
|
23
114
|
)) {
|
|
24
115
|
const connectedSubcircuitKeys = new Set(
|
|
25
116
|
connectedIds
|
|
26
117
|
.map((id) => idToSubcircuitConnectivityKey[id])
|
|
27
118
|
.filter((key): key is string => Boolean(key)),
|
|
28
119
|
)
|
|
29
|
-
|
|
120
|
+
const subcircuitKey = Array.from(connectedSubcircuitKeys).sort()[0]
|
|
121
|
+
if (subcircuitKey) {
|
|
122
|
+
generatedNetIdToSubcircuitConnectivityKey[generatedNetId] = subcircuitKey
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const subcircuitConnectivityMap: Record<string, string> = {}
|
|
127
|
+
for (const [scopedKey, ids] of Object.entries(scopedKeyToIds)) {
|
|
128
|
+
const resolvedKeys = new Set<string>()
|
|
129
|
+
for (const id of ids) {
|
|
130
|
+
const generatedNetId = globalConnectivityMap.getNetConnectedToId(id)
|
|
131
|
+
const resolvedKey = generatedNetId
|
|
132
|
+
? generatedNetIdToSubcircuitConnectivityKey[generatedNetId]
|
|
133
|
+
: undefined
|
|
134
|
+
resolvedKeys.add(resolvedKey ?? scopedKey)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (resolvedKeys.size > 1) {
|
|
30
138
|
throw new Error(
|
|
31
|
-
`
|
|
139
|
+
`subcircuit_connectivity_map_key "${scopedKey}" maps to multiple global connectivity keys: ${Array.from(resolvedKeys).join(", ")}`,
|
|
32
140
|
)
|
|
33
141
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
142
|
+
|
|
143
|
+
const resolvedKey = resolvedKeys.values().next().value
|
|
144
|
+
if (resolvedKey) {
|
|
145
|
+
subcircuitConnectivityMap[scopedKey] = resolvedKey
|
|
37
146
|
}
|
|
38
147
|
}
|
|
39
148
|
|
|
40
149
|
return {
|
|
41
|
-
knownSubcircuitConnectivityKeys: new Set(
|
|
42
|
-
Object.
|
|
43
|
-
|
|
150
|
+
knownSubcircuitConnectivityKeys: new Set([
|
|
151
|
+
...Object.keys(scopedKeyToIds),
|
|
152
|
+
...localSubcircuitConnectivityKeys,
|
|
153
|
+
]),
|
|
154
|
+
descendantSubcircuitIds,
|
|
155
|
+
getScopedSubcircuitConnectivityKey,
|
|
156
|
+
getElementSubcircuitId,
|
|
157
|
+
resolveSubcircuitConnectivityKey(
|
|
158
|
+
subcircuitConnectivityMapKey: string,
|
|
159
|
+
subcircuitId?: string,
|
|
160
|
+
): string {
|
|
161
|
+
const matchingScopedKeys = Object.keys(scopedKeyToIds).filter(
|
|
162
|
+
(scopedKey) =>
|
|
163
|
+
scopedKey ===
|
|
164
|
+
getScopedSubcircuitConnectivityKey(
|
|
165
|
+
subcircuitId,
|
|
166
|
+
subcircuitConnectivityMapKey,
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (
|
|
171
|
+
subcircuitId &&
|
|
172
|
+
matchingScopedKeys.length === 0 &&
|
|
173
|
+
descendantSubcircuitIds
|
|
174
|
+
) {
|
|
175
|
+
matchingScopedKeys.push(
|
|
176
|
+
...Object.keys(scopedKeyToIds).filter((scopedKey) =>
|
|
177
|
+
scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`),
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!subcircuitId) {
|
|
183
|
+
matchingScopedKeys.push(
|
|
184
|
+
...Object.keys(scopedKeyToIds).filter((scopedKey) =>
|
|
185
|
+
scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`),
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const uniqueMatchingScopedKeys = Array.from(new Set(matchingScopedKeys))
|
|
191
|
+
if (uniqueMatchingScopedKeys.length === 0) {
|
|
192
|
+
if (subcircuitId && descendantSubcircuitIds) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`No subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" found in subcircuit "${subcircuitId}" or its child subcircuits.`,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return getScopedSubcircuitConnectivityKey(
|
|
199
|
+
subcircuitId,
|
|
200
|
+
subcircuitConnectivityMapKey,
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
if (uniqueMatchingScopedKeys.length > 1) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" exists in multiple subcircuits. Pass subcircuit_id to disambiguate.`,
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
subcircuitConnectivityMap[uniqueMatchingScopedKeys[0]!] ??
|
|
211
|
+
uniqueMatchingScopedKeys[0]!
|
|
212
|
+
)
|
|
213
|
+
},
|
|
44
214
|
getSubcircuitConnectivityKeyForId(id: string): string | undefined {
|
|
45
|
-
|
|
46
|
-
|
|
215
|
+
if (descendantSubcircuitIds) {
|
|
216
|
+
const subcircuitId = idToSubcircuitId[id]
|
|
217
|
+
if (!subcircuitId || !descendantSubcircuitIds.has(subcircuitId)) {
|
|
218
|
+
return undefined
|
|
219
|
+
}
|
|
220
|
+
}
|
|
47
221
|
|
|
48
|
-
const
|
|
49
|
-
|
|
222
|
+
const directKey = idToSubcircuitConnectivityKey[id]
|
|
223
|
+
const generatedNetId = globalConnectivityMap.getNetConnectedToId(id)
|
|
224
|
+
if (!generatedNetId) {
|
|
225
|
+
return directKey
|
|
226
|
+
? (subcircuitConnectivityMap[directKey] ?? directKey)
|
|
227
|
+
: undefined
|
|
228
|
+
}
|
|
50
229
|
|
|
51
|
-
return
|
|
230
|
+
return (
|
|
231
|
+
generatedNetIdToSubcircuitConnectivityKey[generatedNetId] ??
|
|
232
|
+
(directKey ? subcircuitConnectivityMap[directKey] : undefined) ??
|
|
233
|
+
directKey
|
|
234
|
+
)
|
|
52
235
|
},
|
|
53
236
|
}
|
|
54
237
|
}
|
|
@@ -32,14 +32,19 @@ export const convertCircuitJsonToInputProblem = (
|
|
|
32
32
|
|
|
33
33
|
if (!pcb_board) throw new Error("No pcb_board found in circuit json")
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const globalConnectivityMap =
|
|
36
|
+
getFullConnectivityMapFromCircuitJson(circuitJson)
|
|
37
|
+
const subcircuitConnectivityMap = buildSubcircuitConnectivityLookup(
|
|
38
|
+
circuitJson,
|
|
39
|
+
globalConnectivityMap,
|
|
40
|
+
options.subcircuit_id,
|
|
41
|
+
)
|
|
38
42
|
const pourConnectivityKey = resolvePourConnectivityKey(
|
|
39
43
|
circuitJson,
|
|
40
44
|
options,
|
|
41
|
-
|
|
45
|
+
subcircuitConnectivityMap,
|
|
42
46
|
)
|
|
47
|
+
const { getSubcircuitConnectivityKeyForId } = subcircuitConnectivityMap
|
|
43
48
|
|
|
44
49
|
const pads: InputPad[] = []
|
|
45
50
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import type { AnyCircuitElement, SourceNet } from "circuit-json"
|
|
2
|
+
import type { buildSubcircuitConnectivityLookup } from "./buildSubcircuitConnectivityLookup"
|
|
2
3
|
import type { ConvertCircuitJsonToInputProblemOptions } from "./ConvertCircuitJsonToInputProblemOptions"
|
|
3
4
|
|
|
5
|
+
type SubcircuitConnectivityLookup = ReturnType<
|
|
6
|
+
typeof buildSubcircuitConnectivityLookup
|
|
7
|
+
>
|
|
8
|
+
|
|
4
9
|
export const resolvePourConnectivityKey = (
|
|
5
10
|
circuitJson: AnyCircuitElement[],
|
|
6
11
|
options: ConvertCircuitJsonToInputProblemOptions,
|
|
7
|
-
|
|
12
|
+
subcircuitConnectivityMap: SubcircuitConnectivityLookup,
|
|
8
13
|
): string => {
|
|
9
14
|
if (options.subcircuit_connectivity_map_key) {
|
|
10
|
-
return
|
|
15
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
16
|
+
options.subcircuit_connectivity_map_key,
|
|
17
|
+
options.subcircuit_id,
|
|
18
|
+
)
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
if (options.source_net_id) {
|
|
@@ -24,14 +32,23 @@ export const resolvePourConnectivityKey = (
|
|
|
24
32
|
`source_net "${options.source_net_id}" has no subcircuit_connectivity_map_key`,
|
|
25
33
|
)
|
|
26
34
|
}
|
|
27
|
-
return
|
|
35
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
36
|
+
sourceNet.subcircuit_connectivity_map_key,
|
|
37
|
+
sourceNet.subcircuit_id ?? options.subcircuit_id,
|
|
38
|
+
)
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
if (options.source_net_name) {
|
|
31
42
|
const sourceNet = circuitJson.find(
|
|
32
43
|
(element): element is SourceNet =>
|
|
33
44
|
element.type === "source_net" &&
|
|
34
|
-
element.name === options.source_net_name
|
|
45
|
+
element.name === options.source_net_name &&
|
|
46
|
+
(!options.subcircuit_id ||
|
|
47
|
+
Boolean(
|
|
48
|
+
subcircuitConnectivityMap.descendantSubcircuitIds?.has(
|
|
49
|
+
element.subcircuit_id ?? "",
|
|
50
|
+
),
|
|
51
|
+
)),
|
|
35
52
|
)
|
|
36
53
|
if (!sourceNet) {
|
|
37
54
|
throw new Error(
|
|
@@ -43,16 +60,26 @@ export const resolvePourConnectivityKey = (
|
|
|
43
60
|
`source_net "${options.source_net_name}" has no subcircuit_connectivity_map_key`,
|
|
44
61
|
)
|
|
45
62
|
}
|
|
46
|
-
return
|
|
63
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
64
|
+
sourceNet.subcircuit_connectivity_map_key,
|
|
65
|
+
sourceNet.subcircuit_id ?? options.subcircuit_id,
|
|
66
|
+
)
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
if (options.pour_connectivity_key) {
|
|
50
|
-
if (
|
|
70
|
+
if (
|
|
71
|
+
!subcircuitConnectivityMap.knownSubcircuitConnectivityKeys.has(
|
|
72
|
+
options.pour_connectivity_key,
|
|
73
|
+
)
|
|
74
|
+
) {
|
|
51
75
|
throw new Error(
|
|
52
76
|
`pour_connectivity_key must be a subcircuit_connectivity_map_key. Use subcircuit_connectivity_map_key, source_net_id, or source_net_name instead of a generated connectivity-map id.`,
|
|
53
77
|
)
|
|
54
78
|
}
|
|
55
|
-
return
|
|
79
|
+
return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
|
|
80
|
+
options.pour_connectivity_key,
|
|
81
|
+
options.subcircuit_id,
|
|
82
|
+
)
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/copper-pour-solver",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.36",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"format": "biome format . --write",
|
|
7
7
|
"format:check": "biome format .",
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@biomejs/biome": "^2.2.4",
|
|
16
|
-
"@tscircuit/circuit-json-util": "^0.0.77",
|
|
17
16
|
"@flatten-js/core": "^1.6.2",
|
|
18
|
-
"@tscircuit/
|
|
17
|
+
"@tscircuit/circuit-json-util": "^0.0.77",
|
|
18
|
+
"@tscircuit/core": "^0.0.988",
|
|
19
|
+
"@tscircuit/math-utils": "^0.0.29",
|
|
19
20
|
"@tscircuit/solver-utils": "^0.0.14",
|
|
20
|
-
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
21
21
|
"@types/bun": "latest",
|
|
22
22
|
"bun-match-svg": "^0.0.13",
|
|
23
23
|
"circuit-json": "^0.0.432",
|
|
24
|
+
"circuit-json-to-connectivity-map": "^0.0.23",
|
|
24
25
|
"circuit-to-svg": "^0.0.350",
|
|
25
26
|
"react-cosmos": "^7.0.0",
|
|
26
27
|
"react-cosmos-plugin-vite": "^7.0.0",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" data-software-used-string="@tscircuit/core@0.0.988"><style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="66.66666666666669" y="66.6666666666666" width="666.6666666666667" height="466.66666666666663" data-type="pcb_boundary" data-pcb-layer="global"/><path class="pcb-board" d="M 66.66666666666669 533.3333333333333 L 733.3333333333335 533.3333333333333 L 733.3333333333335 66.6666666666666 L 66.66666666666669 66.6666666666666 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="6.666666666666668" data-type="pcb_board" data-pcb-layer="board"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 200.00000000000006 299.99999999999994 L 332.00000000000006 299.99999999999994" stroke-width="10.666666666666668" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 332.00000000000006 299.99999999999994 L 332.00000000000006 183.33333333333326" stroke-width="10.666666666666668" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 332.00000000000006 183.33333333333326 L 618.0000000000001 183.33333333333326" stroke-width="10.666666666666668" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/><path class="pcb-trace" stroke="rgb(200, 52, 52)" fill="none" d="M 618.0000000000001 183.33333333333326 L 618.0000000000001 299.99999999999994" stroke-width="10.666666666666668" stroke-linecap="round" stroke-linejoin="round" shape-rendering="crispEdges" data-type="pcb_trace" data-pcb-layer="top"/><path class="pcb-copper-pour pcb-copper-pour-brep" d="M 733.3333333333335 533.3333333333333 L 66.66666666666669 533.3333333333333 L 66.66666666666669 66.6666666666666 L 733.3333333333335 66.6666666666666 L 733.3333333333335 533.3333333333333 Z M 382.00000000000006 366.66666666666663 L 515.3333333333334 366.66666666666663 L 515.3333333333334 233.33333333333326 L 382.00000000000006 233.33333333333326 L 382.00000000000006 366.66666666666663 Z" fill="rgb(200, 52, 52)" fill-rule="evenodd" fill-opacity="0.5" data-type="pcb_copper_pour" data-pcb-layer="top"/><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="33.333333333333336" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,200.00000000000006,224.99999999999994)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_0" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">A</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="46.666666666666664" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,200.00000000000006,130.6666666666666)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_1" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">J1</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="33.333333333333336" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,448.66666666666674,224.99999999999994)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_2" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">B</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="33.333333333333336" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,618.0000000000001,224.99999999999994)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_3" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">C</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="46.666666666666664" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,533.3333333333334,130.6666666666666)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_4" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">J2</text><g data-type="pcb_plated_hole" data-pcb-layer="through"><rect class="pcb-hole-outer-pad" fill="rgb(200, 52, 52)" x="150.00000000000006" y="249.99999999999994" width="100" height="100" data-type="pcb_plated_hole" data-pcb-layer="top"/><circle class="pcb-hole-inner" fill="#FF26E2" cx="200.00000000000006" cy="299.99999999999994" r="33.333333333333336" data-type="pcb_plated_hole_drill" data-pcb-layer="drill"/></g><g data-type="pcb_plated_hole" data-pcb-layer="through"><rect class="pcb-hole-outer-pad" fill="rgb(200, 52, 52)" x="398.66666666666674" y="249.99999999999994" width="100" height="100" data-type="pcb_plated_hole" data-pcb-layer="top"/><circle class="pcb-hole-inner" fill="#FF26E2" cx="448.66666666666674" cy="299.99999999999994" r="33.333333333333336" data-type="pcb_plated_hole_drill" data-pcb-layer="drill"/></g><g data-type="pcb_plated_hole" data-pcb-layer="through"><circle class="pcb-hole-outer" fill="rgb(200, 52, 52)" cx="618.0000000000001" cy="299.99999999999994" r="50" data-type="pcb_plated_hole" data-pcb-layer="top"/><circle class="pcb-hole-inner" fill="#FF26E2" cx="618.0000000000001" cy="299.99999999999994" r="33.333333333333336" data-type="pcb_plated_hole_drill" data-pcb-layer="drill"/></g><text x="400.00000000000006" y="9.999999999999943" fill="#ffffff" font-family="Arial, sans-serif" font-size="14.666666666666668" text-anchor="middle" dominant-baseline="central" class="pcb-note-text" data-type="pcb_note_text" data-pcb-note-text-id="pcb_note_text_0" data-pcb-layer="overlay">Expected: copper pour connects to A and C, but clears around B.</text><text x="400.00000000000006" y="29.999999999999943" fill="#ffffff" font-family="Arial, sans-serif" font-size="14.666666666666668" text-anchor="middle" dominant-baseline="central" class="pcb-note-text" data-type="pcb_note_text" data-pcb-note-text-id="pcb_note_text_1" data-pcb-layer="overlay">A is parent net.GND; C is child net.GND reached through the parent-to-child net.</text><text x="400.00000000000006" y="49.999999999999915" fill="#ffffff" font-family="Arial, sans-serif" font-size="14.666666666666668" text-anchor="middle" dominant-baseline="central" class="pcb-note-text" data-type="pcb_note_text" data-pcb-note-text-id="pcb_note_text_2" data-pcb-layer="overlay">B is a separate child pin, so the pour should clear around it.</text></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" data-software-used-string="@tscircuit/core@0.0.988"><style></style><rect class="boundary" x="0" y="0" fill="#000" width="800" height="600" data-type="pcb_background" data-pcb-layer="global"/><rect class="pcb-boundary" fill="none" stroke="#fff" stroke-width="0.3" x="66.66666666666669" y="66.6666666666666" width="666.6666666666667" height="466.66666666666663" data-type="pcb_boundary" data-pcb-layer="global"/><path class="pcb-board" d="M 66.66666666666669 533.3333333333333 L 733.3333333333335 533.3333333333333 L 733.3333333333335 66.6666666666666 L 66.66666666666669 66.6666666666666 Z" fill="none" stroke="rgba(255, 255, 255, 0.5)" stroke-width="6.666666666666668" data-type="pcb_board" data-pcb-layer="board"/><path class="pcb-copper-pour pcb-copper-pour-brep" d="M 733.3333333333335 533.3333333333333 L 66.66666666666669 533.3333333333333 L 66.66666666666669 66.6666666666666 L 733.3333333333335 66.6666666666666 L 733.3333333333335 533.3333333333333 Z M 382.00000000000006 366.66666666666663 L 515.3333333333334 366.66666666666663 L 515.3333333333334 233.33333333333326 L 382.00000000000006 233.33333333333326 L 382.00000000000006 366.66666666666663 Z M 618.0000000000001 366.66666666666663 L 631.0060000000001 365.3856666666666 L 643.5122000000001 361.5919999999999 L 655.038 355.43133333333327 L 665.1404666666667 347.1404666666666 L 673.4313333333334 337.03799999999995 L 679.5920000000001 325.51219999999995 L 683.3856666666668 313.006 L 684.6666666666667 299.99999999999994 L 683.3856666666668 286.9939999999999 L 679.5920000000001 274.48779999999994 L 673.4313333333334 262.96199999999993 L 665.1404666666667 252.85953333333327 L 655.038 244.56866666666662 L 643.5122000000001 238.40799999999993 L 631.0060000000001 234.61433333333326 L 618.0000000000001 233.33333333333326 L 604.9940000000001 234.61433333333326 L 592.4878000000001 238.40799999999993 L 580.9620000000001 244.56866666666662 L 570.8595333333334 252.85953333333327 L 562.5686666666668 262.96199999999993 L 556.4080000000001 274.48779999999994 L 552.6143333333334 286.9939999999999 L 551.3333333333334 299.99999999999994 L 552.6143333333334 313.006 L 556.4080000000001 325.51219999999995 L 562.5686666666668 337.03799999999995 L 570.8595333333334 347.1404666666666 L 580.9620000000001 355.43133333333327 L 592.4878000000001 361.5919999999999 L 604.9940000000001 365.3856666666666 L 618.0000000000001 366.66666666666663 Z" fill="rgb(200, 52, 52)" fill-rule="evenodd" fill-opacity="0.5" data-type="pcb_copper_pour" data-pcb-layer="top"/><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="33.333333333333336" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,200.00000000000006,224.99999999999994)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_0" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">A</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="46.666666666666664" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,200.00000000000006,130.6666666666666)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_1" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">J1</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="33.333333333333336" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,448.66666666666674,224.99999999999994)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_2" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">B</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="33.333333333333336" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,618.0000000000001,224.99999999999994)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_3" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">C</text><text x="0" y="0" dx="0" dy="0" fill="#f2eda1" font-family="Arial, sans-serif" font-size="46.666666666666664" text-anchor="middle" dominant-baseline="central" transform="matrix(1,0,0,1,533.3333333333334,130.6666666666666)" class="pcb-silkscreen-text pcb-silkscreen-top" data-pcb-silkscreen-text-id="pcb_silkscreen_text_4" stroke="none" data-type="pcb_silkscreen_text" data-pcb-layer="top">J2</text><g data-type="pcb_plated_hole" data-pcb-layer="through"><rect class="pcb-hole-outer-pad" fill="rgb(200, 52, 52)" x="150.00000000000006" y="249.99999999999994" width="100" height="100" data-type="pcb_plated_hole" data-pcb-layer="top"/><circle class="pcb-hole-inner" fill="#FF26E2" cx="200.00000000000006" cy="299.99999999999994" r="33.333333333333336" data-type="pcb_plated_hole_drill" data-pcb-layer="drill"/></g><g data-type="pcb_plated_hole" data-pcb-layer="through"><rect class="pcb-hole-outer-pad" fill="rgb(200, 52, 52)" x="398.66666666666674" y="249.99999999999994" width="100" height="100" data-type="pcb_plated_hole" data-pcb-layer="top"/><circle class="pcb-hole-inner" fill="#FF26E2" cx="448.66666666666674" cy="299.99999999999994" r="33.333333333333336" data-type="pcb_plated_hole_drill" data-pcb-layer="drill"/></g><g data-type="pcb_plated_hole" data-pcb-layer="through"><circle class="pcb-hole-outer" fill="rgb(200, 52, 52)" cx="618.0000000000001" cy="299.99999999999994" r="50" data-type="pcb_plated_hole" data-pcb-layer="top"/><circle class="pcb-hole-inner" fill="#FF26E2" cx="618.0000000000001" cy="299.99999999999994" r="33.333333333333336" data-type="pcb_plated_hole_drill" data-pcb-layer="drill"/></g><text x="400.00000000000006" y="9.999999999999943" fill="#ffffff" font-family="Arial, sans-serif" font-size="14.666666666666668" text-anchor="middle" dominant-baseline="central" class="pcb-note-text" data-type="pcb_note_text" data-pcb-note-text-id="pcb_note_text_0" data-pcb-layer="overlay">Expected: copper pour connects to A, but clears around B and C.</text><text x="400.00000000000006" y="29.999999999999943" fill="#ffffff" font-family="Arial, sans-serif" font-size="14.666666666666668" text-anchor="middle" dominant-baseline="central" class="pcb-note-text" data-type="pcb_note_text" data-pcb-note-text-id="pcb_note_text_1" data-pcb-layer="overlay">A is parent net.GND; C is child net.GND with no parent-to-child net.</text><text x="400.00000000000006" y="49.999999999999915" fill="#ffffff" font-family="Arial, sans-serif" font-size="14.666666666666668" text-anchor="middle" dominant-baseline="central" class="pcb-note-text" data-type="pcb_note_text" data-pcb-note-text-id="pcb_note_text_2" data-pcb-layer="overlay">B is a separate child pin, so the pour should clear around it.</text></svg>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "pcb_board",
|
|
4
|
+
"pcb_board_id": "pcb_board_0",
|
|
5
|
+
"center": { "x": 0, "y": 0 },
|
|
6
|
+
"width": 8,
|
|
7
|
+
"height": 4,
|
|
8
|
+
"thickness": 1.4,
|
|
9
|
+
"num_layers": 2,
|
|
10
|
+
"material": "fr4"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "source_group",
|
|
14
|
+
"source_group_id": "source_group_parent",
|
|
15
|
+
"is_subcircuit": true,
|
|
16
|
+
"subcircuit_id": "subcircuit_parent"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "source_group",
|
|
20
|
+
"source_group_id": "source_group_child_a",
|
|
21
|
+
"is_subcircuit": true,
|
|
22
|
+
"subcircuit_id": "subcircuit_child_a",
|
|
23
|
+
"parent_subcircuit_id": "subcircuit_parent"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "source_group",
|
|
27
|
+
"source_group_id": "source_group_child_b",
|
|
28
|
+
"is_subcircuit": true,
|
|
29
|
+
"subcircuit_id": "subcircuit_child_b",
|
|
30
|
+
"parent_subcircuit_id": "subcircuit_parent"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "source_net",
|
|
34
|
+
"source_net_id": "source_net_child_a_gnd",
|
|
35
|
+
"name": "GND",
|
|
36
|
+
"member_source_group_ids": ["source_group_child_a"],
|
|
37
|
+
"subcircuit_id": "subcircuit_child_a",
|
|
38
|
+
"subcircuit_connectivity_map_key": "net0"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "source_trace",
|
|
42
|
+
"source_trace_id": "source_trace_child_a_gnd",
|
|
43
|
+
"connected_source_net_ids": ["source_net_child_a_gnd"],
|
|
44
|
+
"connected_source_port_ids": ["source_port_child_a_gnd"],
|
|
45
|
+
"subcircuit_id": "subcircuit_child_a",
|
|
46
|
+
"subcircuit_connectivity_map_key": "net0"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "source_port",
|
|
50
|
+
"source_port_id": "source_port_child_a_gnd",
|
|
51
|
+
"source_component_id": "source_component_child_a",
|
|
52
|
+
"name": "1",
|
|
53
|
+
"subcircuit_id": "subcircuit_child_a",
|
|
54
|
+
"subcircuit_connectivity_map_key": "net0"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "pcb_port",
|
|
58
|
+
"pcb_port_id": "pcb_port_child_a_gnd",
|
|
59
|
+
"pcb_component_id": "pcb_component_child_a",
|
|
60
|
+
"source_port_id": "source_port_child_a_gnd",
|
|
61
|
+
"x": -2,
|
|
62
|
+
"y": 0,
|
|
63
|
+
"layers": ["top"],
|
|
64
|
+
"subcircuit_id": "subcircuit_child_a"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "pcb_smtpad",
|
|
68
|
+
"pcb_smtpad_id": "pcb_smtpad_child_a_gnd",
|
|
69
|
+
"pcb_component_id": "pcb_component_child_a",
|
|
70
|
+
"pcb_port_id": "pcb_port_child_a_gnd",
|
|
71
|
+
"layer": "top",
|
|
72
|
+
"shape": "rect",
|
|
73
|
+
"x": -2,
|
|
74
|
+
"y": 0,
|
|
75
|
+
"width": 0.8,
|
|
76
|
+
"height": 0.8,
|
|
77
|
+
"subcircuit_id": "subcircuit_child_a"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"type": "source_net",
|
|
81
|
+
"source_net_id": "source_net_child_b_gnd",
|
|
82
|
+
"name": "GND",
|
|
83
|
+
"member_source_group_ids": ["source_group_child_b"],
|
|
84
|
+
"subcircuit_id": "subcircuit_child_b",
|
|
85
|
+
"subcircuit_connectivity_map_key": "net0"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "source_trace",
|
|
89
|
+
"source_trace_id": "source_trace_child_b_gnd",
|
|
90
|
+
"connected_source_net_ids": ["source_net_child_b_gnd"],
|
|
91
|
+
"connected_source_port_ids": ["source_port_child_b_gnd"],
|
|
92
|
+
"subcircuit_id": "subcircuit_child_b",
|
|
93
|
+
"subcircuit_connectivity_map_key": "net0"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "source_port",
|
|
97
|
+
"source_port_id": "source_port_child_b_gnd",
|
|
98
|
+
"source_component_id": "source_component_child_b",
|
|
99
|
+
"name": "1",
|
|
100
|
+
"subcircuit_id": "subcircuit_child_b",
|
|
101
|
+
"subcircuit_connectivity_map_key": "net0"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"type": "pcb_port",
|
|
105
|
+
"pcb_port_id": "pcb_port_child_b_gnd",
|
|
106
|
+
"pcb_component_id": "pcb_component_child_b",
|
|
107
|
+
"source_port_id": "source_port_child_b_gnd",
|
|
108
|
+
"x": 2,
|
|
109
|
+
"y": 0,
|
|
110
|
+
"layers": ["top"],
|
|
111
|
+
"subcircuit_id": "subcircuit_child_b"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"type": "pcb_smtpad",
|
|
115
|
+
"pcb_smtpad_id": "pcb_smtpad_child_b_gnd",
|
|
116
|
+
"pcb_component_id": "pcb_component_child_b",
|
|
117
|
+
"pcb_port_id": "pcb_port_child_b_gnd",
|
|
118
|
+
"layer": "top",
|
|
119
|
+
"shape": "rect",
|
|
120
|
+
"x": 2,
|
|
121
|
+
"y": 0,
|
|
122
|
+
"width": 0.8,
|
|
123
|
+
"height": 0.8,
|
|
124
|
+
"subcircuit_id": "subcircuit_child_b"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
@@ -2,6 +2,7 @@ import { expect, test } from "bun:test"
|
|
|
2
2
|
import type { AnyCircuitElement } from "circuit-json"
|
|
3
3
|
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
|
|
4
4
|
import { convertCircuitJsonToInputProblem } from "lib/circuit-json/convert-circuit-json-to-input-problem"
|
|
5
|
+
import subcircuitConnectivityScopeCircuitJson from "./assets/subcircuit-connectivity-scope.json"
|
|
5
6
|
|
|
6
7
|
const circuitJson = [
|
|
7
8
|
{
|
|
@@ -98,3 +99,48 @@ test("circuit-json adapter rejects generated connectivity map keys", () => {
|
|
|
98
99
|
}),
|
|
99
100
|
).toThrow(/subcircuit_connectivity_map_key/)
|
|
100
101
|
})
|
|
102
|
+
|
|
103
|
+
test("subcircuit_id scopes repeated subcircuit connectivity keys", () => {
|
|
104
|
+
const inputProblem = convertCircuitJsonToInputProblem(
|
|
105
|
+
subcircuitConnectivityScopeCircuitJson as AnyCircuitElement[],
|
|
106
|
+
{
|
|
107
|
+
layer: "top",
|
|
108
|
+
subcircuit_id: "subcircuit_child_a",
|
|
109
|
+
subcircuit_connectivity_map_key: "net0",
|
|
110
|
+
pad_margin: 0.2,
|
|
111
|
+
trace_margin: 0.2,
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const childAPad = inputProblem.pads.find(
|
|
116
|
+
(pad) => pad.padId === "pcb_smtpad_child_a_gnd",
|
|
117
|
+
)
|
|
118
|
+
const childBPad = inputProblem.pads.find(
|
|
119
|
+
(pad) => pad.padId === "pcb_smtpad_child_b_gnd",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
expect(inputProblem.regionsForPour[0]?.connectivityKey).toBe(
|
|
123
|
+
"subcircuit:subcircuit_child_a:connectivity:net0",
|
|
124
|
+
)
|
|
125
|
+
expect(childAPad?.connectivityKey).toBe(
|
|
126
|
+
inputProblem.regionsForPour[0]?.connectivityKey,
|
|
127
|
+
)
|
|
128
|
+
expect(childBPad?.connectivityKey).not.toBe(
|
|
129
|
+
inputProblem.regionsForPour[0]?.connectivityKey,
|
|
130
|
+
)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("parent subcircuit selection rejects ambiguous child connectivity keys", () => {
|
|
134
|
+
expect(() =>
|
|
135
|
+
convertCircuitJsonToInputProblem(
|
|
136
|
+
subcircuitConnectivityScopeCircuitJson as AnyCircuitElement[],
|
|
137
|
+
{
|
|
138
|
+
layer: "top",
|
|
139
|
+
subcircuit_id: "subcircuit_parent",
|
|
140
|
+
subcircuit_connectivity_map_key: "net0",
|
|
141
|
+
pad_margin: 0.2,
|
|
142
|
+
trace_margin: 0.2,
|
|
143
|
+
},
|
|
144
|
+
),
|
|
145
|
+
).toThrow(/multiple subcircuits/)
|
|
146
|
+
})
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { Circuit } from "@tscircuit/core"
|
|
3
|
+
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
|
|
4
|
+
import type {
|
|
5
|
+
AnyCircuitElement,
|
|
6
|
+
PcbCopperPourBRep,
|
|
7
|
+
SourceNet,
|
|
8
|
+
} from "circuit-json"
|
|
9
|
+
import { CopperPourPipelineSolver } from "lib/index"
|
|
10
|
+
import { convertCircuitJsonToInputProblem } from "lib/circuit-json/convert-circuit-json-to-input-problem"
|
|
11
|
+
|
|
12
|
+
const SubcircuitChild = () => (
|
|
13
|
+
<subcircuit
|
|
14
|
+
name="SubcircuitChild"
|
|
15
|
+
pcbX={2}
|
|
16
|
+
pcbY={0}
|
|
17
|
+
autorouter="sequential_trace"
|
|
18
|
+
>
|
|
19
|
+
<pinheader
|
|
20
|
+
name="J2"
|
|
21
|
+
pinCount={2}
|
|
22
|
+
footprint="pinrow2"
|
|
23
|
+
pcbX={0}
|
|
24
|
+
pcbY={0}
|
|
25
|
+
pinLabels={{ pin1: "B", pin2: "C" }}
|
|
26
|
+
showSilkscreenPinLabels
|
|
27
|
+
connections={{ C: "net.GND" }}
|
|
28
|
+
/>
|
|
29
|
+
</subcircuit>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
interface SubcircuitConnectivityReproProps {
|
|
33
|
+
includeParentToChildTrace: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SubcircuitConnectivityRepro = ({
|
|
37
|
+
includeParentToChildTrace,
|
|
38
|
+
}: SubcircuitConnectivityReproProps) => (
|
|
39
|
+
<board
|
|
40
|
+
name="SubcircuitParent"
|
|
41
|
+
width="10mm"
|
|
42
|
+
height="7mm"
|
|
43
|
+
autorouter="sequential_trace"
|
|
44
|
+
>
|
|
45
|
+
<pinheader
|
|
46
|
+
name="J1"
|
|
47
|
+
pinCount={1}
|
|
48
|
+
footprint="pinrow1"
|
|
49
|
+
pcbX={-3}
|
|
50
|
+
pcbY={0}
|
|
51
|
+
pinLabels={{ pin1: "A" }}
|
|
52
|
+
showSilkscreenPinLabels
|
|
53
|
+
connections={{ A: "net.GND" }}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<SubcircuitChild />
|
|
57
|
+
|
|
58
|
+
{includeParentToChildTrace && (
|
|
59
|
+
<trace from="J1.A" to=".SubcircuitChild > net.GND" />
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<copperpour
|
|
63
|
+
name="top_gnd_pour"
|
|
64
|
+
layer="top"
|
|
65
|
+
connectsTo="net.GND"
|
|
66
|
+
padMargin="0.25mm"
|
|
67
|
+
traceMargin="0.2mm"
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<pcbnotetext
|
|
71
|
+
text={
|
|
72
|
+
includeParentToChildTrace
|
|
73
|
+
? "Expected: copper pour connects to A and C, but clears around B."
|
|
74
|
+
: "Expected: copper pour connects to A, but clears around B and C."
|
|
75
|
+
}
|
|
76
|
+
pcbX={0}
|
|
77
|
+
pcbY={4.35}
|
|
78
|
+
fontSize="0.22mm"
|
|
79
|
+
anchorAlignment="center"
|
|
80
|
+
color="#ffffff"
|
|
81
|
+
/>
|
|
82
|
+
<pcbnotetext
|
|
83
|
+
text={
|
|
84
|
+
includeParentToChildTrace
|
|
85
|
+
? "A is parent net.GND; C is child net.GND reached through the parent-to-child net."
|
|
86
|
+
: "A is parent net.GND; C is child net.GND with no parent-to-child net."
|
|
87
|
+
}
|
|
88
|
+
pcbX={0}
|
|
89
|
+
pcbY={4.05}
|
|
90
|
+
fontSize="0.22mm"
|
|
91
|
+
anchorAlignment="center"
|
|
92
|
+
color="#ffffff"
|
|
93
|
+
/>
|
|
94
|
+
<pcbnotetext
|
|
95
|
+
text="B is a separate child pin, so the pour should clear around it."
|
|
96
|
+
pcbX={0}
|
|
97
|
+
pcbY={3.75}
|
|
98
|
+
fontSize="0.22mm"
|
|
99
|
+
anchorAlignment="center"
|
|
100
|
+
color="#ffffff"
|
|
101
|
+
/>
|
|
102
|
+
</board>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const renderCircuitJson = async (
|
|
106
|
+
props: SubcircuitConnectivityReproProps,
|
|
107
|
+
): Promise<AnyCircuitElement[]> => {
|
|
108
|
+
const circuit = new Circuit()
|
|
109
|
+
const originalLog = console.log
|
|
110
|
+
const originalWarn = console.warn
|
|
111
|
+
console.log = () => {}
|
|
112
|
+
console.warn = () => {}
|
|
113
|
+
try {
|
|
114
|
+
circuit.add(<SubcircuitConnectivityRepro {...props} />)
|
|
115
|
+
await circuit.renderUntilSettled()
|
|
116
|
+
return circuit.getCircuitJson()
|
|
117
|
+
} finally {
|
|
118
|
+
console.log = originalLog
|
|
119
|
+
console.warn = originalWarn
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const getPadCenterX = (pad: any) => {
|
|
124
|
+
if (typeof pad.x === "number") return pad.x
|
|
125
|
+
return (pad.bounds.minX + pad.bounds.maxX) / 2
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const getPcbPadConnectivityKeys = (inputProblem: any) => {
|
|
129
|
+
const [aPad, bPad, cPad] = inputProblem.pads
|
|
130
|
+
.filter((pad: any) => pad.shape === "circle" || pad.shape === "rect")
|
|
131
|
+
.sort((a: any, b: any) => getPadCenterX(a) - getPadCenterX(b))
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
a: aPad?.connectivityKey,
|
|
135
|
+
b: bPad?.connectivityKey,
|
|
136
|
+
c: cPad?.connectivityKey,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const runSubcircuitConnectivityCase = async ({
|
|
141
|
+
includeParentToChildTrace,
|
|
142
|
+
snapshotName,
|
|
143
|
+
expectedSourceNetConnectivityKeyCount,
|
|
144
|
+
expectChildGndConnectedToPour,
|
|
145
|
+
}: {
|
|
146
|
+
includeParentToChildTrace: boolean
|
|
147
|
+
snapshotName: string
|
|
148
|
+
expectedSourceNetConnectivityKeyCount: number
|
|
149
|
+
expectChildGndConnectedToPour: boolean
|
|
150
|
+
}) => {
|
|
151
|
+
const circuitJson = await renderCircuitJson({ includeParentToChildTrace })
|
|
152
|
+
const gndSourceNets = circuitJson.filter(
|
|
153
|
+
(element): element is SourceNet =>
|
|
154
|
+
element.type === "source_net" && element.name === "GND",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
expect(gndSourceNets).toHaveLength(2)
|
|
158
|
+
expect(
|
|
159
|
+
new Set(gndSourceNets.map((net) => net.subcircuit_connectivity_map_key))
|
|
160
|
+
.size,
|
|
161
|
+
).toBe(expectedSourceNetConnectivityKeyCount)
|
|
162
|
+
expect(new Set(gndSourceNets.map((net) => net.subcircuit_id)).size).toBe(2)
|
|
163
|
+
|
|
164
|
+
const parentGndSourceNet = gndSourceNets.find((net) =>
|
|
165
|
+
net.subcircuit_connectivity_map_key?.includes("SubcircuitParent"),
|
|
166
|
+
)
|
|
167
|
+
expect(parentGndSourceNet).toBeDefined()
|
|
168
|
+
|
|
169
|
+
const inputProblem = convertCircuitJsonToInputProblem(circuitJson, {
|
|
170
|
+
layer: "top",
|
|
171
|
+
source_net_id: parentGndSourceNet!.source_net_id,
|
|
172
|
+
pad_margin: 0.25,
|
|
173
|
+
trace_margin: 0.2,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const pourConnectivityKey = inputProblem.regionsForPour[0]?.connectivityKey
|
|
177
|
+
const padConnectivityKeys = getPcbPadConnectivityKeys(inputProblem)
|
|
178
|
+
|
|
179
|
+
expect(padConnectivityKeys.a).toBe(pourConnectivityKey)
|
|
180
|
+
expect(padConnectivityKeys.b).not.toBe(pourConnectivityKey)
|
|
181
|
+
if (expectChildGndConnectedToPour) {
|
|
182
|
+
expect(padConnectivityKeys.c).toBe(pourConnectivityKey)
|
|
183
|
+
} else {
|
|
184
|
+
expect(padConnectivityKeys.c).not.toBe(pourConnectivityKey)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const output = new CopperPourPipelineSolver(inputProblem).getOutput()
|
|
188
|
+
const copperPours: PcbCopperPourBRep[] = output.brep_shapes.map(
|
|
189
|
+
(brep_shape, i) => ({
|
|
190
|
+
type: "pcb_copper_pour",
|
|
191
|
+
shape: "brep",
|
|
192
|
+
pcb_copper_pour_id: `pcb_copper_pour_tsx_subcircuit_${i}`,
|
|
193
|
+
layer: "top",
|
|
194
|
+
source_net_id: parentGndSourceNet!.source_net_id,
|
|
195
|
+
brep_shape,
|
|
196
|
+
covered_with_solder_mask: true,
|
|
197
|
+
}),
|
|
198
|
+
)
|
|
199
|
+
const svg = convertCircuitJsonToPcbSvg([
|
|
200
|
+
...circuitJson.filter((element) => element.type !== "pcb_copper_pour"),
|
|
201
|
+
...copperPours,
|
|
202
|
+
] as any)
|
|
203
|
+
|
|
204
|
+
await expect(svg).toMatchSvgSnapshot(import.meta.path, snapshotName)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
test("tsx subcircuit connectivity 01 connects child net through parent-to-child trace", async () => {
|
|
208
|
+
await runSubcircuitConnectivityCase({
|
|
209
|
+
includeParentToChildTrace: true,
|
|
210
|
+
snapshotName: "tsx-subcircuit-connectivity01",
|
|
211
|
+
expectedSourceNetConnectivityKeyCount: 1,
|
|
212
|
+
expectChildGndConnectedToPour: true,
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test("tsx subcircuit connectivity 02 keeps child net separate without parent-to-child trace", async () => {
|
|
217
|
+
await runSubcircuitConnectivityCase({
|
|
218
|
+
includeParentToChildTrace: false,
|
|
219
|
+
snapshotName: "tsx-subcircuit-connectivity02",
|
|
220
|
+
expectedSourceNetConnectivityKeyCount: 2,
|
|
221
|
+
expectChildGndConnectedToPour: false,
|
|
222
|
+
})
|
|
223
|
+
})
|