@tscircuit/schematic-trace-solver 0.0.98 → 0.0.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +310 -220
  3. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +19 -9
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +30 -0
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/generateEndpointCollisionDetours.ts +84 -0
  6. package/package.json +1 -1
  7. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +18 -18
  8. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +7 -7
  9. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +5 -7
  10. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +6 -12
  11. package/tests/examples/__snapshots__/example09.snap.svg +9 -13
  12. package/tests/examples/__snapshots__/example21.snap.svg +30 -32
  13. package/tests/examples/__snapshots__/example29.snap.svg +13 -19
  14. package/tests/repros/__snapshots__/repro129-host-custom-symbol-passives.snap.svg +60 -0
  15. package/tests/repros/assets/repro129-host-custom-symbol-passives.input.json +94 -0
  16. package/tests/repros/repro129-host-custom-symbol-passives.test.ts +16 -0
  17. package/tests/solvers/MspConnectionPairSolver/msp-connection-pair-solver-direct-connection-distance.test.ts +46 -0
  18. package/tests/solvers/SchematicTraceSingleLineSolver2/generate-endpoint-collision-detours.test.ts +38 -0
@@ -1,3 +1,6 @@
1
+ import { distance } from "@tscircuit/math-utils"
2
+ import type { ConnectivityMap } from "connectivity-map"
3
+ import type { GraphicsObject } from "graphics-debug"
1
4
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
2
5
  import type {
3
6
  InputChip,
@@ -5,17 +8,17 @@ import type {
5
8
  InputProblem,
6
9
  PinId,
7
10
  } from "lib/types/InputProblem"
8
- import { ConnectivityMap } from "connectivity-map"
9
- import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
10
- import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
11
- import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
12
- import type { GraphicsObject } from "graphics-debug"
13
11
  import { getColorFromString } from "lib/utils/getColorFromString"
14
- import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
15
12
  import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
13
+ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
14
+ import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
15
+ import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
16
+ import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
16
17
 
17
18
  export type MspConnectionPairId = string
18
19
 
20
+ const getPinPairKey = (pinIds: [PinId, PinId]) => [...pinIds].sort().join("::")
21
+
19
22
  export type MspConnectionPair = {
20
23
  mspPairId: MspConnectionPairId
21
24
  dcConnNetId: string
@@ -36,6 +39,7 @@ export class MspConnectionPairSolver extends BaseSolver {
36
39
 
37
40
  pinMap: Record<string, InputPin & { chipId: string }>
38
41
  userNetIdByPinId: Record<string, string | undefined>
42
+ directConnectionPinPairKeys: Set<string>
39
43
 
40
44
  constructor({ inputProblem }: { inputProblem: InputProblem }) {
41
45
  super()
@@ -62,7 +66,9 @@ export class MspConnectionPairSolver extends BaseSolver {
62
66
 
63
67
  // Build a mapping from PinId to user-provided netId (if any)
64
68
  this.userNetIdByPinId = {}
69
+ this.directConnectionPinPairKeys = new Set()
65
70
  for (const dc of inputProblem.directConnections) {
71
+ this.directConnectionPinPairKeys.add(getPinPairKey(dc.pinIds))
66
72
  if (dc.netId) {
67
73
  const [a, b] = dc.pinIds
68
74
  this.userNetIdByPinId[a] = dc.netId
@@ -105,9 +111,13 @@ export class MspConnectionPairSolver extends BaseSolver {
105
111
  const [pin1, pin2] = directlyConnectedPins
106
112
  const p1 = this.pinMap[pin1!]!
107
113
  const p2 = this.pinMap[pin2!]!
108
- // Enforce max pair distance (use Manhattan to match orthogonal routing metric)
109
- const manhattanDist = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
110
- if (manhattanDist > this.maxMspPairDistance) {
114
+ const pinPairKey = getPinPairKey([pin1!, pin2!])
115
+ // Explicit source traces are classified by straight-line distance when
116
+ // their input is created; named nets retain the orthogonal route metric.
117
+ const pairDistance = this.directConnectionPinPairKeys.has(pinPairKey)
118
+ ? distance(p1, p2)
119
+ : Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
120
+ if (pairDistance > this.maxMspPairDistance) {
111
121
  // Too far apart; skip creating an MSP pair for this net
112
122
  return
113
123
  }
@@ -16,6 +16,7 @@ import {
16
16
  isVertical,
17
17
  segmentOverlapsRectBoundary,
18
18
  } from "./collisions"
19
+ import { generateEndpointCollisionDetours } from "./generateEndpointCollisionDetours"
19
20
  import {
20
21
  type Axis,
21
22
  aabbFromPoints,
@@ -357,6 +358,35 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
357
358
  const isFirstSegment = segIndex === 0
358
359
  const isLastSegment = segIndex === path.length - 2
359
360
 
361
+ if (path.length === 3 && (isFirstSegment || isLastSegment)) {
362
+ const detours = generateEndpointCollisionDetours({
363
+ path,
364
+ collidingSegmentIndex: segIndex,
365
+ obstacle: rect,
366
+ })
367
+ .filter((detour) => {
368
+ const key = pathKey(detour)
369
+ if (this.visited.has(key)) return false
370
+ this.visited.add(key)
371
+ return true
372
+ })
373
+ .sort(
374
+ (a, b) =>
375
+ this.pathLength(a) - this.pathLength(b) ||
376
+ this.getPinBandPenalty(a) - this.getPinBandPenalty(b),
377
+ )
378
+
379
+ for (const detour of detours) {
380
+ const nextCollisionRects = new Set(collisionRects)
381
+ nextCollisionRects.add(rect)
382
+ this.queue.push({
383
+ path: detour,
384
+ collisionRects: nextCollisionRects,
385
+ })
386
+ }
387
+ return
388
+ }
389
+
360
390
  if (isFirstSegment) {
361
391
  // If first segment collides, move the second segment instead
362
392
  if (path.length < 3) {
@@ -0,0 +1,84 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { isHorizontal, isVertical } from "./collisions"
3
+ import { type Axis, midBetweenPointAndRect } from "./mid"
4
+ import type { ObstacleRect } from "./rect"
5
+
6
+ const getSegmentAxis = (start: Point, end: Point): Axis | null => {
7
+ if (isVertical(start, end)) return "x"
8
+ if (isHorizontal(start, end)) return "y"
9
+ return null
10
+ }
11
+
12
+ const hasOnlyNonzeroOrthogonalSegments = (path: Point[]) =>
13
+ path.every((point, index) => {
14
+ const nextPoint = path[index + 1]
15
+ if (!nextPoint) return true
16
+ if (!isHorizontal(point, nextPoint) && !isVertical(point, nextPoint)) {
17
+ return false
18
+ }
19
+ return Math.abs(point.x - nextPoint.x) + Math.abs(point.y - nextPoint.y) > 0
20
+ })
21
+
22
+ export const generateEndpointCollisionDetours = ({
23
+ path,
24
+ collidingSegmentIndex,
25
+ obstacle,
26
+ }: {
27
+ path: Point[]
28
+ collidingSegmentIndex: number
29
+ obstacle: ObstacleRect
30
+ }): Point[][] => {
31
+ if (path.length !== 3) return []
32
+
33
+ const lastSegmentIndex = path.length - 2
34
+ if (
35
+ collidingSegmentIndex !== 0 &&
36
+ collidingSegmentIndex !== lastSegmentIndex
37
+ ) {
38
+ return []
39
+ }
40
+
41
+ const shouldReverse = collidingSegmentIndex === lastSegmentIndex
42
+ const orderedPath = shouldReverse ? [...path].reverse() : path
43
+ const [start, corner, end] = orderedPath
44
+ const firstSegmentAxis = getSegmentAxis(start!, corner!)
45
+ const secondSegmentAxis = getSegmentAxis(corner!, end!)
46
+ if (!firstSegmentAxis || !secondSegmentAxis) return []
47
+ if (firstSegmentAxis === secondSegmentAxis) return []
48
+
49
+ const escapeCoordinates = [
50
+ ...midBetweenPointAndRect(secondSegmentAxis, start!, obstacle),
51
+ ...midBetweenPointAndRect(secondSegmentAxis, end!, obstacle),
52
+ ]
53
+ const detourCoordinates = [
54
+ ...midBetweenPointAndRect(firstSegmentAxis, start!, obstacle),
55
+ ...midBetweenPointAndRect(firstSegmentAxis, end!, obstacle),
56
+ ]
57
+
58
+ const detours: Point[][] = []
59
+ for (const escapeCoordinate of [...new Set(escapeCoordinates)]) {
60
+ for (const detourCoordinate of [...new Set(detourCoordinates)]) {
61
+ const orderedDetour =
62
+ firstSegmentAxis === "y"
63
+ ? [
64
+ start!,
65
+ { x: escapeCoordinate, y: start!.y },
66
+ { x: escapeCoordinate, y: detourCoordinate },
67
+ { x: end!.x, y: detourCoordinate },
68
+ end!,
69
+ ]
70
+ : [
71
+ start!,
72
+ { x: start!.x, y: escapeCoordinate },
73
+ { x: detourCoordinate, y: escapeCoordinate },
74
+ { x: detourCoordinate, y: end!.y },
75
+ end!,
76
+ ]
77
+
78
+ const detour = shouldReverse ? orderedDetour.reverse() : orderedDetour
79
+ if (hasOnlyNonzeroOrthogonalSegments(detour)) detours.push(detour)
80
+ }
81
+ }
82
+
83
+ return detours
84
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.98",
4
+ "version": "0.0.99",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",