@tscircuit/schematic-trace-solver 0.0.197 → 0.0.198

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/dist/index.d.ts CHANGED
@@ -963,6 +963,7 @@ interface RailNetLabelCornerPlacementSolverParams {
963
963
  inputProblem: InputProblem;
964
964
  traces: SolvedTracePath[];
965
965
  netLabelPlacements: NetLabelPlacement[];
966
+ netLabelConnectorTraceIds?: ReadonlySet<string>;
966
967
  }
967
968
  type TraceCornerCandidate = {
968
969
  anchorPoint: Point;
@@ -996,6 +997,7 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
996
997
  private queuedCornerCandidates;
997
998
  private shouldAdvanceToNextLabel;
998
999
  private traceMap;
1000
+ private netLabelConnectorTraceIds;
999
1001
  constructor(params: RailNetLabelCornerPlacementSolverParams);
1000
1002
  getConstructorParams(): ConstructorParameters<typeof RailNetLabelCornerPlacementSolver>[0];
1001
1003
  _step(): void;
@@ -1015,6 +1017,8 @@ declare class RailNetLabelCornerPlacementSolver extends BaseSolver {
1015
1017
  private intersectsAnyChip;
1016
1018
  private intersectsAnyOtherNetLabel;
1017
1019
  private getCornerCandidatesForLabel;
1020
+ private getStraightConnectorCandidates;
1021
+ private isComponentAdjacentCorner;
1018
1022
  private isConfiguredRailLabel;
1019
1023
  private isLabelCrossedByOtherNetTrace;
1020
1024
  private pointsEqual;
package/dist/index.js CHANGED
@@ -11181,6 +11181,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
11181
11181
  queuedCornerCandidates = [];
11182
11182
  shouldAdvanceToNextLabel = false;
11183
11183
  traceMap;
11184
+ netLabelConnectorTraceIds;
11184
11185
  constructor(params) {
11185
11186
  super();
11186
11187
  this.inputProblem = params.inputProblem;
@@ -11190,6 +11191,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
11190
11191
  this.traceMap = Object.fromEntries(
11191
11192
  params.traces.map((trace) => [trace.mspPairId, trace])
11192
11193
  );
11194
+ this.netLabelConnectorTraceIds = params.netLabelConnectorTraceIds ?? /* @__PURE__ */ new Set();
11193
11195
  this.queuedLabelIndices = this.getProcessableLabelIndices();
11194
11196
  this.prepareNextLabel();
11195
11197
  }
@@ -11197,7 +11199,8 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
11197
11199
  return {
11198
11200
  inputProblem: this.inputProblem,
11199
11201
  traces: this.traces,
11200
- netLabelPlacements: this.netLabelPlacements
11202
+ netLabelPlacements: this.netLabelPlacements,
11203
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds
11201
11204
  };
11202
11205
  }
11203
11206
  _step() {
@@ -11248,7 +11251,10 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
11248
11251
  this.currentLabelIndex = labelIndex;
11249
11252
  this.currentLabel = label;
11250
11253
  this.currentCandidateResults = [];
11251
- this.queuedCornerCandidates = this.getCornerCandidatesForLabel(label);
11254
+ this.queuedCornerCandidates = [
11255
+ ...this.getStraightConnectorCandidates(label),
11256
+ ...this.getCornerCandidatesForLabel(label)
11257
+ ];
11252
11258
  return true;
11253
11259
  }
11254
11260
  advanceToNextLabel() {
@@ -11330,7 +11336,7 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
11330
11336
  }
11331
11337
  shouldProcessLabel(label) {
11332
11338
  const isVerticalRailLabel = label.orientation === "y+" || label.orientation === "y-";
11333
- return isVerticalRailLabel && this.getCornerCandidatesForLabel(label).length > 0;
11339
+ return isVerticalRailLabel && (this.getStraightConnectorCandidates(label).length > 0 || this.getCornerCandidatesForLabel(label).length > 0);
11334
11340
  }
11335
11341
  intersectsAnyChip(bounds) {
11336
11342
  return this.inputProblem.chips.some(
@@ -11387,6 +11393,77 @@ var RailNetLabelCornerPlacementSolver = class extends BaseSolver {
11387
11393
  ...railAlignedCandidates.sort((a, b) => a.distance - b.distance)
11388
11394
  ];
11389
11395
  }
11396
+ getStraightConnectorCandidates(label) {
11397
+ if (label.pinIds.length !== 2 || !this.isConfiguredRailLabel(label)) {
11398
+ return [];
11399
+ }
11400
+ const pins = this.inputProblem.chips.flatMap((chip) => chip.pins).filter((pin) => label.pinIds.includes(pin.pinId));
11401
+ if (pins.length !== 2 || pins[0]._facingDirection !== pins[1]._facingDirection || pins[0]._facingDirection !== "x+" && pins[0]._facingDirection !== "x-" || Math.abs(pins[0].y - pins[1].y) > EPS8) {
11402
+ return [];
11403
+ }
11404
+ const configuredOrientations = this.inputProblem.availableNetLabelOrientations[label.netId];
11405
+ if (configuredOrientations?.length !== 1 || configuredOrientations[0] !== label.orientation) {
11406
+ return [];
11407
+ }
11408
+ const netConnection = this.inputProblem.netConnections.find(
11409
+ (connection) => connection.netId === label.netId
11410
+ );
11411
+ if (netConnection?.isGround) return [];
11412
+ const connector = this.traces.find(
11413
+ (trace) => this.netLabelConnectorTraceIds.has(trace.mspPairId) && trace.globalConnNetId === label.globalConnNetId && label.pinIds.every((pinId) => trace.pinIds.includes(pinId)) && trace.tracePath.length >= 2 && (this.pointsEqual(trace.tracePath[0], label.anchorPoint) || this.pointsEqual(trace.tracePath.at(-1), label.anchorPoint))
11414
+ );
11415
+ if (!connector) return [];
11416
+ const sourcePoint = this.pointsEqual(
11417
+ connector.tracePath[0],
11418
+ label.anchorPoint
11419
+ ) ? connector.tracePath.at(-1) : connector.tracePath[0];
11420
+ if (!this.isComponentAdjacentCorner(label, sourcePoint)) return [];
11421
+ const direction = label.orientation === "y+" ? 1 : -1;
11422
+ const maxSearchDistance = Math.min(
11423
+ getMaxSearchDistance(this.inputProblem),
11424
+ label.height * 2
11425
+ );
11426
+ for (let distance7 = WICK_CLEARANCE + LABEL_SEARCH_STEP; distance7 <= maxSearchDistance + EPS8; distance7 += LABEL_SEARCH_STEP) {
11427
+ const anchorPoint = {
11428
+ x: sourcePoint.x,
11429
+ y: sourcePoint.y + direction * distance7
11430
+ };
11431
+ const center = getCenterFromAnchor(
11432
+ anchorPoint,
11433
+ label.orientation,
11434
+ label.width,
11435
+ label.height
11436
+ );
11437
+ if (this.intersectsAnyChip(getRectBounds(center, label.width, label.height))) {
11438
+ continue;
11439
+ }
11440
+ return [
11441
+ {
11442
+ anchorPoint,
11443
+ traceId: connector.mspPairId,
11444
+ distance: getDistance(anchorPoint, label.anchorPoint),
11445
+ pinAligned: true,
11446
+ reroutedTracePath: [sourcePoint, anchorPoint]
11447
+ }
11448
+ ];
11449
+ }
11450
+ return [];
11451
+ }
11452
+ isComponentAdjacentCorner(label, corner) {
11453
+ return label.mspConnectionPairIds.some((traceId) => {
11454
+ const trace = this.traceMap[traceId];
11455
+ if (!trace || trace.tracePath.length < 2) return false;
11456
+ return [
11457
+ { point: trace.tracePath[0], neighbor: trace.tracePath[1] },
11458
+ {
11459
+ point: trace.tracePath.at(-1),
11460
+ neighbor: trace.tracePath.at(-2)
11461
+ }
11462
+ ].some(
11463
+ ({ point, neighbor }) => this.pointsEqual(neighbor, corner) && trace.pins.some((pin) => this.pointsEqual(pin, point))
11464
+ );
11465
+ });
11466
+ }
11390
11467
  isConfiguredRailLabel(label) {
11391
11468
  if (!label.netId) return false;
11392
11469
  return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
@@ -18710,7 +18787,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
18710
18787
  traces: Object.values(
18711
18788
  instance.postLabelTraceOverlapShiftSolver.correctedTraceMap
18712
18789
  ),
18713
- netLabelPlacements: instance.availableNetOrientationSolver.outputNetLabelPlacements
18790
+ netLabelPlacements: instance.availableNetOrientationSolver.outputNetLabelPlacements,
18791
+ netLabelConnectorTraceIds: instance.availableNetOrientationSolver.netLabelConnectorTraceIds
18714
18792
  }
18715
18793
  ];
18716
18794
  }
@@ -1,9 +1,14 @@
1
1
  import type { GraphicsObject } from "graphics-debug"
2
2
  import type { Point } from "@tscircuit/math-utils"
3
3
  import {
4
+ getMaxSearchDistance,
4
5
  segmentCrossesBoundsInterior,
5
6
  traceCrossesBoundsInterior,
6
7
  } from "lib/solvers/AvailableNetOrientationSolver/geometry"
8
+ import {
9
+ LABEL_SEARCH_STEP,
10
+ WICK_CLEARANCE,
11
+ } from "lib/solvers/AvailableNetOrientationSolver/constants"
7
12
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
8
13
  import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
9
14
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
@@ -51,6 +56,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
51
56
  private queuedCornerCandidates: TraceCornerCandidate[] = []
52
57
  private shouldAdvanceToNextLabel = false
53
58
  private traceMap: Record<string, SolvedTracePath>
59
+ private netLabelConnectorTraceIds: ReadonlySet<string>
54
60
 
55
61
  constructor(params: RailNetLabelCornerPlacementSolverParams) {
56
62
  super()
@@ -61,6 +67,8 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
61
67
  this.traceMap = Object.fromEntries(
62
68
  params.traces.map((trace) => [trace.mspPairId, trace]),
63
69
  )
70
+ this.netLabelConnectorTraceIds =
71
+ params.netLabelConnectorTraceIds ?? new Set()
64
72
  this.queuedLabelIndices = this.getProcessableLabelIndices()
65
73
  this.prepareNextLabel()
66
74
  }
@@ -72,6 +80,7 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
72
80
  inputProblem: this.inputProblem,
73
81
  traces: this.traces,
74
82
  netLabelPlacements: this.netLabelPlacements,
83
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
75
84
  }
76
85
  }
77
86
 
@@ -135,7 +144,10 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
135
144
  this.currentLabelIndex = labelIndex
136
145
  this.currentLabel = label
137
146
  this.currentCandidateResults = []
138
- this.queuedCornerCandidates = this.getCornerCandidatesForLabel(label)
147
+ this.queuedCornerCandidates = [
148
+ ...this.getStraightConnectorCandidates(label),
149
+ ...this.getCornerCandidatesForLabel(label),
150
+ ]
139
151
 
140
152
  return true
141
153
  }
@@ -250,7 +262,9 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
250
262
  const isVerticalRailLabel =
251
263
  label.orientation === "y+" || label.orientation === "y-"
252
264
  return (
253
- isVerticalRailLabel && this.getCornerCandidatesForLabel(label).length > 0
265
+ isVerticalRailLabel &&
266
+ (this.getStraightConnectorCandidates(label).length > 0 ||
267
+ this.getCornerCandidatesForLabel(label).length > 0)
254
268
  )
255
269
  }
256
270
 
@@ -323,6 +337,118 @@ export class RailNetLabelCornerPlacementSolver extends BaseSolver {
323
337
  ]
324
338
  }
325
339
 
340
+ private getStraightConnectorCandidates(
341
+ label: NetLabelPlacement,
342
+ ): TraceCornerCandidate[] {
343
+ if (label.pinIds.length !== 2 || !this.isConfiguredRailLabel(label)) {
344
+ return []
345
+ }
346
+
347
+ const pins = this.inputProblem.chips
348
+ .flatMap((chip) => chip.pins)
349
+ .filter((pin) => label.pinIds.includes(pin.pinId))
350
+ if (
351
+ pins.length !== 2 ||
352
+ pins[0]!._facingDirection !== pins[1]!._facingDirection ||
353
+ (pins[0]!._facingDirection !== "x+" &&
354
+ pins[0]!._facingDirection !== "x-") ||
355
+ Math.abs(pins[0]!.y - pins[1]!.y) > EPS
356
+ ) {
357
+ return []
358
+ }
359
+
360
+ const configuredOrientations =
361
+ this.inputProblem.availableNetLabelOrientations[label.netId!]
362
+ if (
363
+ configuredOrientations?.length !== 1 ||
364
+ configuredOrientations[0] !== label.orientation
365
+ ) {
366
+ return []
367
+ }
368
+
369
+ const netConnection = this.inputProblem.netConnections.find(
370
+ (connection) => connection.netId === label.netId,
371
+ )
372
+ if (netConnection?.isGround) return []
373
+
374
+ const connector = this.traces.find(
375
+ (trace) =>
376
+ this.netLabelConnectorTraceIds.has(trace.mspPairId) &&
377
+ trace.globalConnNetId === label.globalConnNetId &&
378
+ label.pinIds.every((pinId) => trace.pinIds.includes(pinId)) &&
379
+ trace.tracePath.length >= 2 &&
380
+ (this.pointsEqual(trace.tracePath[0]!, label.anchorPoint) ||
381
+ this.pointsEqual(trace.tracePath.at(-1)!, label.anchorPoint)),
382
+ )
383
+ if (!connector) return []
384
+
385
+ const sourcePoint = this.pointsEqual(
386
+ connector.tracePath[0]!,
387
+ label.anchorPoint,
388
+ )
389
+ ? connector.tracePath.at(-1)!
390
+ : connector.tracePath[0]!
391
+ if (!this.isComponentAdjacentCorner(label, sourcePoint)) return []
392
+
393
+ const direction = label.orientation === "y+" ? 1 : -1
394
+ const maxSearchDistance = Math.min(
395
+ getMaxSearchDistance(this.inputProblem),
396
+ label.height * 2,
397
+ )
398
+ for (
399
+ let distance = WICK_CLEARANCE + LABEL_SEARCH_STEP;
400
+ distance <= maxSearchDistance + EPS;
401
+ distance += LABEL_SEARCH_STEP
402
+ ) {
403
+ const anchorPoint = {
404
+ x: sourcePoint.x,
405
+ y: sourcePoint.y + direction * distance,
406
+ }
407
+ const center = getCenterFromAnchor(
408
+ anchorPoint,
409
+ label.orientation,
410
+ label.width,
411
+ label.height,
412
+ )
413
+ if (
414
+ this.intersectsAnyChip(getRectBounds(center, label.width, label.height))
415
+ ) {
416
+ continue
417
+ }
418
+
419
+ return [
420
+ {
421
+ anchorPoint,
422
+ traceId: connector.mspPairId,
423
+ distance: getDistance(anchorPoint, label.anchorPoint),
424
+ pinAligned: true,
425
+ reroutedTracePath: [sourcePoint, anchorPoint],
426
+ },
427
+ ]
428
+ }
429
+
430
+ return []
431
+ }
432
+
433
+ private isComponentAdjacentCorner(label: NetLabelPlacement, corner: Point) {
434
+ return label.mspConnectionPairIds.some((traceId) => {
435
+ const trace = this.traceMap[traceId]
436
+ if (!trace || trace.tracePath.length < 2) return false
437
+
438
+ return [
439
+ { point: trace.tracePath[0]!, neighbor: trace.tracePath[1]! },
440
+ {
441
+ point: trace.tracePath.at(-1)!,
442
+ neighbor: trace.tracePath.at(-2)!,
443
+ },
444
+ ].some(
445
+ ({ point, neighbor }) =>
446
+ this.pointsEqual(neighbor, corner) &&
447
+ trace.pins.some((pin) => this.pointsEqual(pin, point)),
448
+ )
449
+ })
450
+ }
451
+
326
452
  private isConfiguredRailLabel(label: NetLabelPlacement) {
327
453
  if (!label.netId) return false
328
454
  return this.inputProblem.availableNetLabelOrientations[label.netId]?.some(
@@ -7,6 +7,7 @@ export interface RailNetLabelCornerPlacementSolverParams {
7
7
  inputProblem: InputProblem
8
8
  traces: SolvedTracePath[]
9
9
  netLabelPlacements: NetLabelPlacement[]
10
+ netLabelConnectorTraceIds?: ReadonlySet<string>
10
11
  }
11
12
 
12
13
  export type Bounds = {
@@ -402,6 +402,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
402
402
  ),
403
403
  netLabelPlacements:
404
404
  instance.availableNetOrientationSolver!.outputNetLabelPlacements,
405
+ netLabelConnectorTraceIds:
406
+ instance.availableNetOrientationSolver!.netLabelConnectorTraceIds,
405
407
  },
406
408
  ]
407
409
  },
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.197",
8
+ "version": "0.0.198",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -0,0 +1,7 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
3
+ import input from "../../tests/repros/assets/repro-am3352-sheet3-power-rails.input.json"
4
+
5
+ export default () => (
6
+ <PipelineDebugger inputProblem={structuredClone(input) as InputProblem} />
7
+ )
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260911T195723Z/bug-report-20260911T195723Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />