@tscircuit/fanout-solver 0.0.32 → 0.0.33

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.
@@ -10,7 +10,6 @@ import {
10
10
  distancePointToSegment,
11
11
  pointIsInsideObstacle,
12
12
  } from "./geometry"
13
- import { getCopperLayerNames, getLayerSpan } from "./layer-names"
14
13
  import { obstacleSharesElectricalNet } from "./net-identity"
15
14
  import type {
16
15
  FanoutEndpointCompletionReport,
@@ -593,58 +592,6 @@ function trimCompletedFanoutTails(params: {
593
592
  return { ...fanoutSrj, traces }
594
593
  }
595
594
 
596
- function createFanoutCopperObstacles(srj: SimpleRouteJson): Obstacle[] {
597
- const layerNames = getCopperLayerNames(srj.layerCount)
598
- const obstacles: Obstacle[] = []
599
- for (const trace of srj.traces ?? []) {
600
- let previousWire:
601
- | Extract<SimplifiedPcbTrace["route"][number], { route_type: "wire" }>
602
- | undefined
603
- for (const point of trace.route) {
604
- if (point.route_type === "via") {
605
- obstacles.push({
606
- obstacleId: `endpoint-completion-keepout:${trace.pcb_trace_id}:via:${obstacles.length}`,
607
- type: "rect",
608
- center: { x: point.x, y: point.y },
609
- width: point.via_diameter ?? srj.minViaPadDiameter ?? 0.3,
610
- height: point.via_diameter ?? srj.minViaPadDiameter ?? 0.3,
611
- layers: getLayerSpan(point.from_layer, point.to_layer, layerNames),
612
- connectedTo: [trace.connection_name],
613
- })
614
- previousWire = undefined
615
- continue
616
- }
617
- if (point.route_type !== "wire") {
618
- previousWire = undefined
619
- continue
620
- }
621
- if (previousWire?.layer === point.layer) {
622
- const segmentLength = distance(previousWire, point)
623
- if (segmentLength > EPSILON) {
624
- obstacles.push({
625
- obstacleId: `endpoint-completion-keepout:${trace.pcb_trace_id}:segment:${obstacles.length}`,
626
- type: "rect",
627
- center: {
628
- x: (previousWire.x + point.x) / 2,
629
- y: (previousWire.y + point.y) / 2,
630
- },
631
- width: segmentLength,
632
- height: Math.max(previousWire.width, point.width),
633
- ccwRotationDegrees:
634
- (Math.atan2(point.y - previousWire.y, point.x - previousWire.x) *
635
- 180) /
636
- Math.PI,
637
- layers: [point.layer],
638
- connectedTo: [trace.connection_name],
639
- })
640
- }
641
- }
642
- previousWire = point
643
- }
644
- }
645
- return obstacles
646
- }
647
-
648
595
  function acceptDownstreamTraces(params: {
649
596
  inputSrj: SimpleRouteJson
650
597
  fanoutSrj: SimpleRouteJson
@@ -1092,11 +1039,8 @@ export function completeOriginalEndpoints(params: {
1092
1039
  ),
1093
1040
  }))
1094
1041
  .filter((bus) => bus.connectionNames.length > 0),
1095
- obstacles: [
1096
- ...fanoutSrj.obstacles,
1097
- ...createFanoutCopperObstacles(fanoutSrj),
1098
- ],
1099
- traces: [],
1042
+ obstacles: fanoutSrj.obstacles,
1043
+ traces: directSrj.traces,
1100
1044
  }
1101
1045
  try {
1102
1046
  const candidates = routeDownstreamConnections(downstreamInput, { effort })
@@ -374,6 +374,7 @@ export class FanoutSolver extends BaseSolver {
374
374
  readonly attempts: FanoutAttemptSummary[] = []
375
375
  readonly layerAssignments: Array<Readonly<Record<string, string>>>
376
376
  readonly config: ResolvedFanoutConfig
377
+ private readonly routingSrj: SimpleRouteJson
377
378
  private readonly escapeLayersByBusId: Readonly<
378
379
  Record<string, readonly string[]>
379
380
  >
@@ -404,8 +405,12 @@ export class FanoutSolver extends BaseSolver {
404
405
  public readonly options: FanoutSolverOptions = {},
405
406
  ) {
406
407
  super()
408
+ this.routingSrj = {
409
+ ...inputSrj,
410
+ obstacles: [...inputSrj.obstacles],
411
+ }
407
412
  this.config = resolveConfig(inputSrj, options)
408
- this.preparedBuses = prepareFanoutBuses(inputSrj, options)
413
+ this.preparedBuses = prepareFanoutBuses(this.routingSrj, options)
409
414
  for (const bus of this.preparedBuses) {
410
415
  for (const allowedLayer of bus.allowedLayers ?? []) {
411
416
  if (!this.config.layerNames.includes(allowedLayer)) {
@@ -468,7 +473,7 @@ export class FanoutSolver extends BaseSolver {
468
473
  bus.busId,
469
474
  getCandidateEscapeLayersForBus({
470
475
  bus,
471
- srj: inputSrj,
476
+ srj: this.routingSrj,
472
477
  config: this.config,
473
478
  staticClearanceCache: this.routeStaticClearanceCache,
474
479
  }),
@@ -511,7 +516,7 @@ export class FanoutSolver extends BaseSolver {
511
516
  return
512
517
  }
513
518
  this.endpointCompletion = completeOriginalEndpoints({
514
- inputSrj: this.inputSrj,
519
+ inputSrj: this.routingSrj,
515
520
  fanoutSrj: this.bestAttempt.outputSrj,
516
521
  plans: this.bestAttempt.plans,
517
522
  traceWidth: this.config.traceWidth,
@@ -563,7 +568,7 @@ export class FanoutSolver extends BaseSolver {
563
568
  const isSingleLayerFanout = this.config.escapeLayers.length === 1
564
569
  if (isSingleLayerFanout && this.config.singleLayerPushAndShove) {
565
570
  const singleLayerParams = {
566
- srj: this.inputSrj,
571
+ srj: this.routingSrj,
567
572
  buses: this.preparedBuses,
568
573
  traceWidth: this.config.traceWidth,
569
574
  clearance: this.config.clearance,
@@ -628,7 +633,7 @@ export class FanoutSolver extends BaseSolver {
628
633
  }
629
634
  const currentBusBlockingCounts = new Map<string, number>()
630
635
  const busPlans = routeBus({
631
- srj: this.inputSrj,
636
+ srj: this.routingSrj,
632
637
  bus,
633
638
  targetLayer,
634
639
  acceptedPlans: plans,
@@ -884,7 +889,7 @@ export class FanoutSolver extends BaseSolver {
884
889
  for (const targetLayer of orderedLayers) {
885
890
  const busAlternatives = routeBusAlternatives(
886
891
  {
887
- srj: this.inputSrj,
892
+ srj: this.routingSrj,
888
893
  bus,
889
894
  targetLayer,
890
895
  acceptedPlans: state.plans,
@@ -0,0 +1,83 @@
1
+ import type {
2
+ SimpleRouteJson,
3
+ SimplifiedPcbTrace,
4
+ } from "@tscircuit/capacity-autorouter"
5
+ import { getCopperLayerNames, getLayerSpan } from "./layer-names"
6
+ import type { Point2D, RoutedSegment } from "./types"
7
+
8
+ export interface RoutedTraceVia {
9
+ center: Point2D
10
+ diameter: number
11
+ spanLayers: string[]
12
+ }
13
+
14
+ export interface RoutedTraceCopper {
15
+ trace: SimplifiedPcbTrace
16
+ connectionName: string
17
+ segments: RoutedSegment[]
18
+ vias: RoutedTraceVia[]
19
+ }
20
+
21
+ export function getRoutedTraceCopper(
22
+ srj: SimpleRouteJson,
23
+ trace: SimplifiedPcbTrace,
24
+ ): RoutedTraceCopper {
25
+ const layerNames = getCopperLayerNames(srj.layerCount)
26
+ const segments: RoutedSegment[] = []
27
+ const vias: RoutedTraceVia[] = []
28
+ let previousWire:
29
+ | Extract<SimplifiedPcbTrace["route"][number], { route_type: "wire" }>
30
+ | undefined
31
+
32
+ for (const routePoint of trace.route) {
33
+ if (routePoint.route_type === "via") {
34
+ vias.push({
35
+ center: { x: routePoint.x, y: routePoint.y },
36
+ diameter:
37
+ routePoint.via_diameter ??
38
+ srj.minViaPadDiameter ??
39
+ srj.min_via_pad_diameter ??
40
+ srj.minViaDiameter ??
41
+ srj.minTraceWidth,
42
+ spanLayers: getLayerSpan(
43
+ routePoint.from_layer,
44
+ routePoint.to_layer,
45
+ layerNames,
46
+ ),
47
+ })
48
+ previousWire = undefined
49
+ continue
50
+ }
51
+
52
+ if (routePoint.route_type !== "wire") {
53
+ previousWire = undefined
54
+ continue
55
+ }
56
+
57
+ if (
58
+ previousWire?.layer === routePoint.layer &&
59
+ (previousWire.x !== routePoint.x || previousWire.y !== routePoint.y)
60
+ ) {
61
+ segments.push({
62
+ start: { x: previousWire.x, y: previousWire.y },
63
+ end: { x: routePoint.x, y: routePoint.y },
64
+ width: Math.max(previousWire.width, routePoint.width),
65
+ layer: routePoint.layer,
66
+ })
67
+ }
68
+ previousWire = routePoint
69
+ }
70
+
71
+ return {
72
+ trace,
73
+ connectionName: trace.connection_name,
74
+ segments,
75
+ vias,
76
+ }
77
+ }
78
+
79
+ export function getAllRoutedTraceCopper(
80
+ srj: SimpleRouteJson,
81
+ ): RoutedTraceCopper[] {
82
+ return (srj.traces ?? []).map((trace) => getRoutedTraceCopper(srj, trace))
83
+ }
@@ -62,12 +62,16 @@ function createElectricalNetIdentity(
62
62
  }
63
63
 
64
64
  for (const trace of srj.traces ?? []) {
65
- const netKey = trace.connection_name
66
- ? connectionNetKeys.get(trace.connection_name)
67
- : undefined
68
- if (!netKey) continue
69
- addTokenNet(tokenNetKeys, trace.pcb_trace_id, netKey)
70
- for (const token of trace.connectsTo ?? []) {
65
+ const traceTokens = [
66
+ trace.connection_name,
67
+ trace.pcb_trace_id,
68
+ ...(trace.connectsTo ?? []),
69
+ ]
70
+ const knownNetKeys = getKnownNetKeys(tokenNetKeys, traceTokens)
71
+ if (knownNetKeys.size !== 1) continue
72
+ const netKey = [...knownNetKeys][0]!
73
+ connectionNetKeys.set(trace.connection_name, netKey)
74
+ for (const token of traceTokens) {
71
75
  addTokenNet(tokenNetKeys, token, netKey)
72
76
  }
73
77
  }
package/lib/route-bus.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  segmentsAreClear,
12
12
  } from "./geometry"
13
13
  import { createFanoutOutputIds } from "./fanout-output-ids"
14
+ import { getAllRoutedTraceCopper } from "./get-routed-trace-copper"
14
15
  import { getLayerSpan } from "./layer-names"
15
16
  import {
16
17
  connectionsShareElectricalNet,
@@ -1000,6 +1001,66 @@ function planIsStaticallyClear(params: {
1000
1001
  }
1001
1002
  }
1002
1003
 
1004
+ for (const traceCopper of getAllRoutedTraceCopper(srj)) {
1005
+ if (
1006
+ plan.connectionName === traceCopper.connectionName ||
1007
+ (allowSameNetMerges &&
1008
+ connectionsShareElectricalNet(
1009
+ srj,
1010
+ plan.connectionName,
1011
+ traceCopper.connectionName,
1012
+ ))
1013
+ ) {
1014
+ continue
1015
+ }
1016
+ for (const segment of getPlanSegments(plan)) {
1017
+ for (const existingSegment of traceCopper.segments) {
1018
+ if (!segmentsAreClear(segment, existingSegment, clearance)) {
1019
+ return false
1020
+ }
1021
+ }
1022
+ for (const existingVia of traceCopper.vias) {
1023
+ if (
1024
+ existingVia.spanLayers.includes(segment.layer) &&
1025
+ distancePointToSegment(
1026
+ existingVia.center,
1027
+ segment.start,
1028
+ segment.end,
1029
+ ) <
1030
+ existingVia.diameter / 2 + segment.width / 2 + clearance - 1e-9
1031
+ ) {
1032
+ return false
1033
+ }
1034
+ }
1035
+ }
1036
+ for (const via of getPlanVias(plan)) {
1037
+ for (const existingSegment of traceCopper.segments) {
1038
+ if (
1039
+ via.spanLayers.includes(existingSegment.layer) &&
1040
+ distancePointToSegment(
1041
+ via.center,
1042
+ existingSegment.start,
1043
+ existingSegment.end,
1044
+ ) <
1045
+ via.diameter / 2 + existingSegment.width / 2 + clearance - 1e-9
1046
+ ) {
1047
+ return false
1048
+ }
1049
+ }
1050
+ for (const existingVia of traceCopper.vias) {
1051
+ if (
1052
+ via.spanLayers.some((layer) =>
1053
+ existingVia.spanLayers.includes(layer),
1054
+ ) &&
1055
+ distance(via.center, existingVia.center) <
1056
+ (via.diameter + existingVia.diameter) / 2 + clearance - 1e-9
1057
+ ) {
1058
+ return false
1059
+ }
1060
+ }
1061
+ }
1062
+ }
1063
+
1003
1064
  return true
1004
1065
  }
1005
1066
 
@@ -15,6 +15,7 @@ import {
15
15
  connectionsShareElectricalNet,
16
16
  obstacleSharesElectricalNet,
17
17
  } from "./net-identity"
18
+ import { getAllRoutedTraceCopper } from "./get-routed-trace-copper"
18
19
  import type {
19
20
  Bounds,
20
21
  FanoutRoutePlan,
@@ -664,6 +665,89 @@ function validateClearances(params: {
664
665
  }
665
666
  }
666
667
  }
668
+
669
+ for (const traceCopper of getAllRoutedTraceCopper(inputSrj)) {
670
+ if (
671
+ plan.connectionName === traceCopper.connectionName ||
672
+ connectionsShareElectricalNet(
673
+ inputSrj,
674
+ plan.connectionName,
675
+ traceCopper.connectionName,
676
+ )
677
+ ) {
678
+ continue
679
+ }
680
+ for (const segment of getPlanSegments(plan)) {
681
+ for (const existingSegment of traceCopper.segments) {
682
+ if (!segmentsAreClear(segment, existingSegment, clearance)) {
683
+ addIssue(
684
+ issues,
685
+ "different-net-trace-clearance",
686
+ `Trace ${plan.connectionName} violates routed trace ${traceCopper.trace.pcb_trace_id} on ${segment.layer}`,
687
+ plan,
688
+ traceCopper.connectionName,
689
+ )
690
+ }
691
+ }
692
+ for (const existingVia of traceCopper.vias) {
693
+ if (
694
+ existingVia.spanLayers.includes(segment.layer) &&
695
+ distancePointToSegment(
696
+ existingVia.center,
697
+ segment.start,
698
+ segment.end,
699
+ ) <
700
+ existingVia.diameter / 2 + segment.width / 2 + clearance - 1e-9
701
+ ) {
702
+ addIssue(
703
+ issues,
704
+ "different-net-trace-via-clearance",
705
+ `Trace ${plan.connectionName} violates a via in routed trace ${traceCopper.trace.pcb_trace_id} on ${segment.layer}`,
706
+ plan,
707
+ traceCopper.connectionName,
708
+ )
709
+ }
710
+ }
711
+ }
712
+ for (const via of getPlanVias(plan)) {
713
+ for (const existingSegment of traceCopper.segments) {
714
+ if (
715
+ via.spanLayers.includes(existingSegment.layer) &&
716
+ distancePointToSegment(
717
+ via.center,
718
+ existingSegment.start,
719
+ existingSegment.end,
720
+ ) <
721
+ via.diameter / 2 + existingSegment.width / 2 + clearance - 1e-9
722
+ ) {
723
+ addIssue(
724
+ issues,
725
+ "different-net-trace-via-clearance",
726
+ `Via ${plan.connectionName} violates routed trace ${traceCopper.trace.pcb_trace_id} on ${existingSegment.layer}`,
727
+ plan,
728
+ traceCopper.connectionName,
729
+ )
730
+ }
731
+ }
732
+ for (const existingVia of traceCopper.vias) {
733
+ if (
734
+ via.spanLayers.some((layer) =>
735
+ existingVia.spanLayers.includes(layer),
736
+ ) &&
737
+ distance(via.center, existingVia.center) <
738
+ (via.diameter + existingVia.diameter) / 2 + clearance - 1e-9
739
+ ) {
740
+ addIssue(
741
+ issues,
742
+ "different-net-via-clearance",
743
+ `Via ${plan.connectionName} violates a via in routed trace ${traceCopper.trace.pcb_trace_id}`,
744
+ plan,
745
+ traceCopper.connectionName,
746
+ )
747
+ }
748
+ }
749
+ }
750
+ }
667
751
  }
668
752
 
669
753
  for (let firstIndex = 0; firstIndex < plans.length; firstIndex++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",