@tscircuit/schematic-trace-solver 0.0.183 → 0.0.184

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 (103) hide show
  1. package/dist/index.js +231 -20
  2. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +30 -12
  3. package/lib/solvers/SameNetJunctionAlignmentSolver/collapseRedundantSharedEndpointStubs.ts +267 -0
  4. package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +1 -1
  5. package/lib/solvers/SameNetJunctionAlignmentSolver/getSharedPin.ts +13 -0
  6. package/package.json +1 -1
  7. package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +2 -2
  8. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +2 -2
  9. package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +2 -2
  10. package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.test.ts +10 -0
  11. package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +2 -2
  12. package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +2 -2
  13. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +2 -2
  14. package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +2 -2
  15. package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +2 -2
  16. package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +2 -2
  17. package/tests/bug-reports/bug-report-20260708T095725Z/__snapshots__/bug-report-20260708T095725Z.snap.svg +2 -2
  18. package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +2 -2
  19. package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +2 -2
  20. package/tests/bug-reports/bug-report-20260717T042845Z/__snapshots__/bug-report-20260717T042845Z.snap.svg +2 -2
  21. package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
  22. package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +2 -2
  23. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
  24. package/tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.test.ts +2 -0
  25. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  26. package/tests/bug-reports/bug-report-20260730T061837Z/bug-report-20260730T061837Z.test.ts +1 -1
  27. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +2 -2
  28. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
  29. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +2 -2
  30. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +2 -2
  31. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +2 -2
  32. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +2 -2
  33. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +2 -2
  34. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +2 -2
  35. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +2 -2
  36. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +2 -2
  37. package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +2 -2
  38. package/tests/bug-reports/bug-report-20260901T064117Z/__snapshots__/bug-report-20260901T064117Z.snap.svg +2 -2
  39. package/tests/bug-reports/bug-report-20260901T134241Z/__snapshots__/bug-report-20260901T134241Z.snap.svg +2 -2
  40. package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +2 -2
  41. package/tests/examples/__snapshots__/example02.snap.svg +2 -2
  42. package/tests/examples/__snapshots__/example13.snap.svg +2 -2
  43. package/tests/examples/__snapshots__/example14.snap.svg +2 -2
  44. package/tests/examples/__snapshots__/example15.snap.svg +2 -2
  45. package/tests/examples/__snapshots__/example18.snap.svg +2 -2
  46. package/tests/examples/__snapshots__/example19.snap.svg +2 -2
  47. package/tests/examples/__snapshots__/example21.snap.svg +2 -2
  48. package/tests/examples/__snapshots__/example27.snap.svg +2 -2
  49. package/tests/examples/__snapshots__/example29.snap.svg +2 -2
  50. package/tests/examples/__snapshots__/example42.snap.svg +2 -2
  51. package/tests/examples/__snapshots__/example45.snap.svg +2 -2
  52. package/tests/examples/__snapshots__/example46.snap.svg +2 -2
  53. package/tests/examples/__snapshots__/example47.snap.svg +2 -2
  54. package/tests/examples/__snapshots__/example51.snap.svg +2 -2
  55. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +2 -2
  56. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
  57. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +2 -2
  58. package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +2 -2
  59. package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +2 -2
  60. package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +2 -2
  61. package/tests/repros/__snapshots__/repro-bc107a-common-emitter-routing.snap.svg +2 -2
  62. package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +2 -2
  63. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +2 -2
  64. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +2 -2
  65. package/tests/repros/__snapshots__/repro-board-8090-power-section.snap.svg +2 -2
  66. package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +2 -2
  67. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
  68. package/tests/repros/__snapshots__/repro-bq40z60-current-sense.snap.svg +2 -2
  69. package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +2 -2
  70. package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +2 -2
  71. package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +508 -0
  72. package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
  73. package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +2 -2
  74. package/tests/repros/__snapshots__/repro-hdc3020-ground-label-alignment.snap.svg +2 -2
  75. package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +2 -2
  76. package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +2 -2
  77. package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +2 -2
  78. package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +2 -2
  79. package/tests/repros/__snapshots__/repro-pga300-c6-trace-alignment.snap.svg +2 -2
  80. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +2 -2
  81. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +18 -18
  82. package/tests/repros/__snapshots__/repro-power-group-rail-labels.snap.svg +2 -2
  83. package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +2 -2
  84. package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +2 -2
  85. package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -2
  86. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +2 -2
  87. package/tests/repros/__snapshots__/repro-smartwatch-power-sheet.snap.svg +2 -2
  88. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +2 -2
  89. package/tests/repros/__snapshots__/repro-tida-01389-hbridge.snap.svg +2 -2
  90. package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +2 -2
  91. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +2 -2
  92. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
  93. package/tests/repros/__snapshots__/repro-tmp1075-ground-label-overlap.snap.svg +2 -2
  94. package/tests/repros/__snapshots__/repro-tps61222-trace-intersection.snap.svg +2 -2
  95. package/tests/repros/__snapshots__/repro-trellis-core-gnd-net-label-overlap.snap.svg +2 -2
  96. package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +2 -2
  97. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +2 -2
  98. package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +2 -2
  99. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +2 -2
  100. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +2 -2
  101. package/tests/repros/assets/repro-esp12f-status-section.input.json +1394 -0
  102. package/tests/repros/repro-esp12f-section.test.ts +12 -0
  103. package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-redundant-shared-endpoint-stubs.test.ts +101 -0
package/dist/index.js CHANGED
@@ -13230,24 +13230,228 @@ var findNearestSharedPinExitRail = ({
13230
13230
  return nearestRail;
13231
13231
  };
13232
13232
 
13233
+ // lib/solvers/SameNetJunctionAlignmentSolver/getSharedPin.ts
13234
+ var getSharedPin = ({
13235
+ donorTrace,
13236
+ branchTrace
13237
+ }) => {
13238
+ const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId));
13239
+ return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null;
13240
+ };
13241
+
13242
+ // lib/solvers/SameNetJunctionAlignmentSolver/collapseRedundantSharedEndpointStubs.ts
13243
+ var getSegmentAxis3 = (start, end) => {
13244
+ if (nearlyEqual(start.x, end.x)) return "y";
13245
+ if (nearlyEqual(start.y, end.y)) return "x";
13246
+ return null;
13247
+ };
13248
+ var getPathFromSharedPin = ({
13249
+ trace,
13250
+ sharedPinId
13251
+ }) => {
13252
+ const tracePath = simplifyPath(trace.tracePath);
13253
+ if (trace.pins[0]?.pinId === sharedPinId) return tracePath;
13254
+ return tracePath.reverse();
13255
+ };
13256
+ var traceCanLoseSharedPrefix = ({
13257
+ trace,
13258
+ sharedPrefix,
13259
+ netLabelPlacements
13260
+ }) => {
13261
+ return !netLabelPlacements.some(
13262
+ (label) => label.mspConnectionPairIds.includes(trace.mspPairId) && tracePathContainsPoint(sharedPrefix, label.anchorPoint)
13263
+ );
13264
+ };
13265
+ var getTraceWithoutSharedPrefix = ({
13266
+ trace,
13267
+ pathFromSharedPin,
13268
+ sharedPinId,
13269
+ branchPoint
13270
+ }) => {
13271
+ const branchSegmentIndex = pathFromSharedPin.findIndex((point, index) => {
13272
+ const nextPoint = pathFromSharedPin[index + 1];
13273
+ if (!nextPoint) return false;
13274
+ return tracePathContainsPoint([point, nextPoint], branchPoint);
13275
+ });
13276
+ if (branchSegmentIndex < 0) return null;
13277
+ const pathFromJunction = simplifyPath([
13278
+ branchPoint,
13279
+ ...pathFromSharedPin.slice(branchSegmentIndex + 1)
13280
+ ]);
13281
+ let tracePath = pathFromJunction;
13282
+ if (trace.pins[0]?.pinId !== sharedPinId) {
13283
+ tracePath = pathFromJunction.reverse();
13284
+ }
13285
+ return { ...trace, tracePath };
13286
+ };
13287
+ var collapseRedundantSharedEndpointStubs = ({
13288
+ traces,
13289
+ netLabelPlacements,
13290
+ netLabelConnectorTraceIds,
13291
+ multiPinNetPinIds
13292
+ }) => {
13293
+ const outputTraces = [...traces];
13294
+ for (let firstIndex = 0; firstIndex < outputTraces.length; firstIndex++) {
13295
+ const firstTrace = outputTraces[firstIndex];
13296
+ if (netLabelConnectorTraceIds.has(firstTrace.mspPairId)) continue;
13297
+ for (let secondIndex = firstIndex + 1; secondIndex < outputTraces.length; secondIndex++) {
13298
+ const secondTrace = outputTraces[secondIndex];
13299
+ if (netLabelConnectorTraceIds.has(secondTrace.mspPairId) || firstTrace.globalConnNetId !== secondTrace.globalConnNetId) {
13300
+ continue;
13301
+ }
13302
+ const sharedPin = getSharedPin({
13303
+ donorTrace: firstTrace,
13304
+ branchTrace: secondTrace
13305
+ });
13306
+ if (!sharedPin || !multiPinNetPinIds.has(sharedPin.pinId)) continue;
13307
+ const firstPath = getPathFromSharedPin({
13308
+ trace: firstTrace,
13309
+ sharedPinId: sharedPin.pinId
13310
+ });
13311
+ const secondPath = getPathFromSharedPin({
13312
+ trace: secondTrace,
13313
+ sharedPinId: sharedPin.pinId
13314
+ });
13315
+ if (firstPath.length < 2 || secondPath.length < 2) continue;
13316
+ const departureAxis = getSegmentAxis3(firstPath[0], firstPath[1]);
13317
+ const secondDepartureAxis = getSegmentAxis3(secondPath[0], secondPath[1]);
13318
+ if (!departureAxis || departureAxis !== secondDepartureAxis) continue;
13319
+ const firstDepartureDirection = firstPath[1][departureAxis] - firstPath[0][departureAxis];
13320
+ const secondDepartureDirection = secondPath[1][departureAxis] - secondPath[0][departureAxis];
13321
+ if (!pointsEqual2(firstPath[1], secondPath[1]) && Math.sign(firstDepartureDirection) === Math.sign(secondDepartureDirection)) {
13322
+ let traceIndexToCollapse2 = secondIndex;
13323
+ let traceToCollapse2 = secondTrace;
13324
+ let pathToCollapse2 = secondPath;
13325
+ if (Math.abs(firstDepartureDirection) < Math.abs(secondDepartureDirection)) {
13326
+ traceIndexToCollapse2 = firstIndex;
13327
+ traceToCollapse2 = firstTrace;
13328
+ pathToCollapse2 = firstPath;
13329
+ }
13330
+ const branchPoint2 = pathToCollapse2[1];
13331
+ if (!traceCanLoseSharedPrefix({
13332
+ trace: traceToCollapse2,
13333
+ sharedPrefix: [pathToCollapse2[0], branchPoint2],
13334
+ netLabelPlacements
13335
+ })) {
13336
+ if (traceIndexToCollapse2 === firstIndex) {
13337
+ traceIndexToCollapse2 = secondIndex;
13338
+ traceToCollapse2 = secondTrace;
13339
+ pathToCollapse2 = secondPath;
13340
+ } else {
13341
+ traceIndexToCollapse2 = firstIndex;
13342
+ traceToCollapse2 = firstTrace;
13343
+ pathToCollapse2 = firstPath;
13344
+ }
13345
+ }
13346
+ if (!traceCanLoseSharedPrefix({
13347
+ trace: traceToCollapse2,
13348
+ sharedPrefix: [pathToCollapse2[0], branchPoint2],
13349
+ netLabelPlacements
13350
+ })) {
13351
+ continue;
13352
+ }
13353
+ const collapsedTrace2 = getTraceWithoutSharedPrefix({
13354
+ trace: traceToCollapse2,
13355
+ pathFromSharedPin: pathToCollapse2,
13356
+ sharedPinId: sharedPin.pinId,
13357
+ branchPoint: branchPoint2
13358
+ });
13359
+ if (!collapsedTrace2) continue;
13360
+ outputTraces[traceIndexToCollapse2] = collapsedTrace2;
13361
+ break;
13362
+ }
13363
+ if (firstPath.length < 3 || secondPath.length < 3) continue;
13364
+ if (!pointsEqual2(firstPath[1], secondPath[1])) continue;
13365
+ const railAxis = getSegmentAxis3(firstPath[1], firstPath[2]);
13366
+ const secondRailAxis = getSegmentAxis3(secondPath[1], secondPath[2]);
13367
+ if (!railAxis || railAxis !== secondRailAxis || railAxis === departureAxis) {
13368
+ continue;
13369
+ }
13370
+ const firstRailDirection = firstPath[2][railAxis] - firstPath[1][railAxis];
13371
+ const secondRailDirection = secondPath[2][railAxis] - secondPath[1][railAxis];
13372
+ const railsHaveSameDirection = Math.sign(firstRailDirection) === Math.sign(secondRailDirection);
13373
+ const sharedStub = firstPath.slice(0, 2);
13374
+ if (!railsHaveSameDirection && !nearlyEqual(
13375
+ Math.abs(firstRailDirection),
13376
+ Math.abs(secondRailDirection)
13377
+ ) && netLabelPlacements.some(
13378
+ (label) => label.globalConnNetId === firstTrace.globalConnNetId && tracePathContainsPoint(sharedStub, label.anchorPoint)
13379
+ )) {
13380
+ continue;
13381
+ }
13382
+ let traceIndexToCollapse = secondIndex;
13383
+ let traceToCollapse = secondTrace;
13384
+ let pathToCollapse = secondPath;
13385
+ let branchPoint = firstPath[1];
13386
+ if (railsHaveSameDirection && Math.abs(firstRailDirection) < Math.abs(secondRailDirection)) {
13387
+ traceIndexToCollapse = firstIndex;
13388
+ traceToCollapse = firstTrace;
13389
+ pathToCollapse = firstPath;
13390
+ branchPoint = firstPath[2];
13391
+ } else if (railsHaveSameDirection) {
13392
+ branchPoint = secondPath[2];
13393
+ }
13394
+ const sharedPrefix = [pathToCollapse[0], pathToCollapse[1], branchPoint];
13395
+ if (!traceCanLoseSharedPrefix({
13396
+ trace: traceToCollapse,
13397
+ sharedPrefix,
13398
+ netLabelPlacements
13399
+ })) {
13400
+ if (traceIndexToCollapse === firstIndex) {
13401
+ traceIndexToCollapse = secondIndex;
13402
+ traceToCollapse = secondTrace;
13403
+ pathToCollapse = secondPath;
13404
+ } else {
13405
+ traceIndexToCollapse = firstIndex;
13406
+ traceToCollapse = firstTrace;
13407
+ pathToCollapse = firstPath;
13408
+ }
13409
+ }
13410
+ if (!traceCanLoseSharedPrefix({
13411
+ trace: traceToCollapse,
13412
+ sharedPrefix: [pathToCollapse[0], pathToCollapse[1], branchPoint],
13413
+ netLabelPlacements
13414
+ })) {
13415
+ continue;
13416
+ }
13417
+ const collapsedTrace = getTraceWithoutSharedPrefix({
13418
+ trace: traceToCollapse,
13419
+ pathFromSharedPin: pathToCollapse,
13420
+ sharedPinId: sharedPin.pinId,
13421
+ branchPoint
13422
+ });
13423
+ if (!collapsedTrace) continue;
13424
+ outputTraces[traceIndexToCollapse] = collapsedTrace;
13425
+ break;
13426
+ }
13427
+ }
13428
+ return outputTraces;
13429
+ };
13430
+
13233
13431
  // lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts
13234
13432
  var MAX_ALIGNED_LOAD_PIN_OFFSET = 0.2;
13235
13433
  var MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2;
13236
13434
  var MAX_SHARED_PIN_RAIL_OFFSET = 0.05;
13237
13435
  var MAX_SHARED_ENDPOINT_RAIL_OFFSET = 0.8;
13238
13436
  var MIN_RETURN_STEM_LENGTH = 0.05;
13239
- var getSegmentAxis3 = (start, end) => {
13437
+ var getMultiPinNetPinIds = (inputProblem) => {
13438
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
13439
+ const inputPinIds = new Set(
13440
+ inputProblem.chips.flatMap((chip) => chip.pins.map((pin) => pin.pinId))
13441
+ );
13442
+ return new Set(
13443
+ [...inputPinIds].filter((pinId) => {
13444
+ const globalNetId = netConnMap.getNetConnectedToId(pinId);
13445
+ if (!globalNetId) return false;
13446
+ return netConnMap.getIdsConnectedToNet(globalNetId).filter((connectedId) => inputPinIds.has(connectedId)).length > 2;
13447
+ })
13448
+ );
13449
+ };
13450
+ var getSegmentAxis4 = (start, end) => {
13240
13451
  if (isHorizontal2(start, end)) return "x";
13241
13452
  if (isVertical2(start, end)) return "y";
13242
13453
  return null;
13243
13454
  };
13244
- var getSharedPin = ({
13245
- donorTrace,
13246
- branchTrace
13247
- }) => {
13248
- const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId));
13249
- return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null;
13250
- };
13251
13455
  var getOtherPin = ({
13252
13456
  trace,
13253
13457
  sharedPin
@@ -13473,8 +13677,8 @@ var getAlignedSharedEndpointRailPath = ({
13473
13677
  const donorPath = pathFromShared(donorTrace);
13474
13678
  const branchPath = pathFromShared(branchTrace);
13475
13679
  if (donorPath.length < 3 || branchPath.length < 4) return null;
13476
- const donorDepartureAxis = getSegmentAxis3(donorPath[0], donorPath[1]);
13477
- const branchDepartureAxis = getSegmentAxis3(branchPath[0], branchPath[1]);
13680
+ const donorDepartureAxis = getSegmentAxis4(donorPath[0], donorPath[1]);
13681
+ const branchDepartureAxis = getSegmentAxis4(branchPath[0], branchPath[1]);
13478
13682
  if (!donorDepartureAxis || donorDepartureAxis !== branchDepartureAxis) {
13479
13683
  return null;
13480
13684
  }
@@ -13482,8 +13686,8 @@ var getAlignedSharedEndpointRailPath = ({
13482
13686
  const branchDeparture = branchPath[1][branchDepartureAxis] - branchPath[0][branchDepartureAxis];
13483
13687
  if (Math.sign(donorDeparture) !== Math.sign(branchDeparture)) return null;
13484
13688
  let railIndex = 1;
13485
- let donorRailAxis = getSegmentAxis3(donorPath[1], donorPath[2]);
13486
- let branchRailAxis = getSegmentAxis3(branchPath[1], branchPath[2]);
13689
+ let donorRailAxis = getSegmentAxis4(donorPath[1], donorPath[2]);
13690
+ let branchRailAxis = getSegmentAxis4(branchPath[1], branchPath[2]);
13487
13691
  if (!donorRailAxis || donorRailAxis !== branchRailAxis || donorRailAxis === donorDepartureAxis) {
13488
13692
  return null;
13489
13693
  }
@@ -13491,11 +13695,11 @@ var getAlignedSharedEndpointRailPath = ({
13491
13695
  if (firstRailsAreAligned) {
13492
13696
  railIndex++;
13493
13697
  if (!donorPath[railIndex + 2] || !branchPath[railIndex + 2]) return null;
13494
- donorRailAxis = getSegmentAxis3(
13698
+ donorRailAxis = getSegmentAxis4(
13495
13699
  donorPath[railIndex],
13496
13700
  donorPath[railIndex + 1]
13497
13701
  );
13498
- branchRailAxis = getSegmentAxis3(
13702
+ branchRailAxis = getSegmentAxis4(
13499
13703
  branchPath[railIndex],
13500
13704
  branchPath[railIndex + 1]
13501
13705
  );
@@ -13503,7 +13707,7 @@ var getAlignedSharedEndpointRailPath = ({
13503
13707
  }
13504
13708
  let branchParallelRailCount = 0;
13505
13709
  for (let index = 1; index < branchPath.length - 2; index++) {
13506
- if (getSegmentAxis3(branchPath[index], branchPath[index + 1]) === branchRailAxis) {
13710
+ if (getSegmentAxis4(branchPath[index], branchPath[index + 1]) === branchRailAxis) {
13507
13711
  branchParallelRailCount++;
13508
13712
  }
13509
13713
  }
@@ -13545,13 +13749,13 @@ var getAlignedAttachedLabelConnectorPath = ({
13545
13749
  const junction = connectorPath[0];
13546
13750
  const turn = connectorPath[1];
13547
13751
  const railEnd = connectorPath[2];
13548
- const departureAxis = getSegmentAxis3(junction, turn);
13549
- const railAxis = getSegmentAxis3(turn, railEnd);
13752
+ const departureAxis = getSegmentAxis4(junction, turn);
13753
+ const railAxis = getSegmentAxis4(turn, railEnd);
13550
13754
  if (!departureAxis || !railAxis || departureAxis === railAxis) return null;
13551
13755
  const donorSegmentIndex = donorTrace.tracePath.findIndex(
13552
13756
  (point, index, path) => {
13553
13757
  const next = path[index + 1];
13554
- return next && getSegmentAxis3(point, next) === departureAxis && tracePathContainsPoint([point, next], junction) && tracePathContainsPoint([point, next], turn);
13758
+ return next && getSegmentAxis4(point, next) === departureAxis && tracePathContainsPoint([point, next], junction) && tracePathContainsPoint([point, next], turn);
13555
13759
  }
13556
13760
  );
13557
13761
  if (donorSegmentIndex < 0) return null;
@@ -13564,7 +13768,7 @@ var getAlignedAttachedLabelConnectorPath = ({
13564
13768
  donorSegmentIndex > 0 ? [donorTrace.tracePath[donorSegmentIndex - 1], donorSegmentStart] : null,
13565
13769
  donorSegmentIndex + 2 < donorTrace.tracePath.length ? [donorSegmentEnd, donorTrace.tracePath[donorSegmentIndex + 2]] : null
13566
13770
  ].flatMap(
13567
- (segment) => segment && getSegmentAxis3(segment[0], segment[1]) === railAxis ? [segment] : []
13771
+ (segment) => segment && getSegmentAxis4(segment[0], segment[1]) === railAxis ? [segment] : []
13568
13772
  );
13569
13773
  if (adjacentDonorRails.length === 0) return null;
13570
13774
  const railCoordinateAxis = railAxis === "x" ? "y" : "x";
@@ -13647,7 +13851,7 @@ var preserveAttachedLabelConnectorJunctions = ({
13647
13851
  if (!movedJunction) return null;
13648
13852
  const neighborIndex = endpointIndex === 0 ? 1 : endpointIndex - 1;
13649
13853
  const neighbor = connector.tracePath[neighborIndex];
13650
- if (!neighbor || getSegmentAxis3(originalJunction, neighbor) !== getSegmentAxis3(movedJunction, neighbor)) {
13854
+ if (!neighbor || getSegmentAxis4(originalJunction, neighbor) !== getSegmentAxis4(movedJunction, neighbor)) {
13651
13855
  return null;
13652
13856
  }
13653
13857
  const movedConnector = {
@@ -14070,6 +14274,13 @@ var alignSameNetJunctions = ({
14070
14274
  }
14071
14275
  }
14072
14276
  }
14277
+ const collapsedSharedEndpointStubs = collapseRedundantSharedEndpointStubs({
14278
+ traces: outputTraces,
14279
+ netLabelPlacements: outputNetLabelPlacements,
14280
+ netLabelConnectorTraceIds,
14281
+ multiPinNetPinIds: getMultiPinNetPinIds(inputProblem)
14282
+ });
14283
+ outputTraces = collapsedSharedEndpointStubs;
14073
14284
  return {
14074
14285
  traces: outputTraces,
14075
14286
  netLabelPlacements: outputNetLabelPlacements,
@@ -2,6 +2,7 @@ import type { Point } from "@tscircuit/math-utils"
2
2
  import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections"
3
3
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
4
  import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
5
+ import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
5
6
  import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
6
7
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
7
8
  import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
@@ -20,7 +21,7 @@ import {
20
21
  isVertical,
21
22
  nearlyEqual,
22
23
  } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
23
- import type { InputPin, InputProblem } from "lib/types/InputProblem"
24
+ import type { InputPin, InputProblem, PinId } from "lib/types/InputProblem"
24
25
  import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
25
26
  import {
26
27
  pathEntersAnyNetLabel,
@@ -31,6 +32,8 @@ import {
31
32
  getPinFacingAxis,
32
33
  isCoordinateOnPinFacingSide,
33
34
  } from "./findNearestSharedPinExitRail"
35
+ import { collapseRedundantSharedEndpointStubs } from "./collapseRedundantSharedEndpointStubs"
36
+ import { getSharedPin } from "./getSharedPin"
34
37
 
35
38
  interface AlignSameNetJunctionsInput {
36
39
  inputProblem: InputProblem
@@ -57,23 +60,30 @@ const MAX_SHARED_PIN_RAIL_OFFSET = 0.05
57
60
  const MAX_SHARED_ENDPOINT_RAIL_OFFSET = 0.8
58
61
  const MIN_RETURN_STEM_LENGTH = 0.05
59
62
 
63
+ const getMultiPinNetPinIds = (inputProblem: InputProblem) => {
64
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
65
+ const inputPinIds = new Set<PinId>(
66
+ inputProblem.chips.flatMap((chip) => chip.pins.map((pin) => pin.pinId)),
67
+ )
68
+ return new Set(
69
+ [...inputPinIds].filter((pinId) => {
70
+ const globalNetId = netConnMap.getNetConnectedToId(pinId)
71
+ if (!globalNetId) return false
72
+ return (
73
+ netConnMap
74
+ .getIdsConnectedToNet(globalNetId)
75
+ .filter((connectedId) => inputPinIds.has(connectedId)).length > 2
76
+ )
77
+ }),
78
+ )
79
+ }
80
+
60
81
  const getSegmentAxis = (start: Point, end: Point) => {
61
82
  if (isHorizontal(start, end)) return "x" as const
62
83
  if (isVertical(start, end)) return "y" as const
63
84
  return null
64
85
  }
65
86
 
66
- export const getSharedPin = ({
67
- donorTrace,
68
- branchTrace,
69
- }: {
70
- donorTrace: SolvedTracePath
71
- branchTrace: SolvedTracePath
72
- }): (InputPin & { chipId: string }) | null => {
73
- const branchPinIds = new Set(branchTrace.pins.map((pin) => pin.pinId))
74
- return donorTrace.pins.find((pin) => branchPinIds.has(pin.pinId)) ?? null
75
- }
76
-
77
87
  const getOtherPin = ({
78
88
  trace,
79
89
  sharedPin,
@@ -1301,6 +1311,14 @@ export const alignSameNetJunctions = ({
1301
1311
  }
1302
1312
  }
1303
1313
 
1314
+ const collapsedSharedEndpointStubs = collapseRedundantSharedEndpointStubs({
1315
+ traces: outputTraces,
1316
+ netLabelPlacements: outputNetLabelPlacements,
1317
+ netLabelConnectorTraceIds,
1318
+ multiPinNetPinIds: getMultiPinNetPinIds(inputProblem),
1319
+ })
1320
+ outputTraces = collapsedSharedEndpointStubs
1321
+
1304
1322
  return {
1305
1323
  traces: outputTraces,
1306
1324
  netLabelPlacements: outputNetLabelPlacements,
@@ -0,0 +1,267 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { PinId } from "lib/types/InputProblem"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
5
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import {
7
+ nearlyEqual,
8
+ pointsEqual,
9
+ } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
10
+ import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
11
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
12
+ import { getSharedPin } from "./getSharedPin"
13
+
14
+ const getSegmentAxis = (start: Point, end: Point) => {
15
+ if (nearlyEqual(start.x, end.x)) return "y" as const
16
+ if (nearlyEqual(start.y, end.y)) return "x" as const
17
+ return null
18
+ }
19
+
20
+ const getPathFromSharedPin = ({
21
+ trace,
22
+ sharedPinId,
23
+ }: {
24
+ trace: SolvedTracePath
25
+ sharedPinId: PinId
26
+ }) => {
27
+ const tracePath = simplifyPath(trace.tracePath)
28
+ if (trace.pins[0]?.pinId === sharedPinId) return tracePath
29
+ return tracePath.reverse()
30
+ }
31
+
32
+ const traceCanLoseSharedPrefix = ({
33
+ trace,
34
+ sharedPrefix,
35
+ netLabelPlacements,
36
+ }: {
37
+ trace: SolvedTracePath
38
+ sharedPrefix: Point[]
39
+ netLabelPlacements: NetLabelPlacement[]
40
+ }) => {
41
+ return !netLabelPlacements.some(
42
+ (label) =>
43
+ label.mspConnectionPairIds.includes(trace.mspPairId) &&
44
+ tracePathContainsPoint(sharedPrefix, label.anchorPoint),
45
+ )
46
+ }
47
+
48
+ const getTraceWithoutSharedPrefix = ({
49
+ trace,
50
+ pathFromSharedPin,
51
+ sharedPinId,
52
+ branchPoint,
53
+ }: {
54
+ trace: SolvedTracePath
55
+ pathFromSharedPin: Point[]
56
+ sharedPinId: PinId
57
+ branchPoint: Point
58
+ }) => {
59
+ const branchSegmentIndex = pathFromSharedPin.findIndex((point, index) => {
60
+ const nextPoint = pathFromSharedPin[index + 1]
61
+ if (!nextPoint) return false
62
+ return tracePathContainsPoint([point, nextPoint], branchPoint)
63
+ })
64
+ if (branchSegmentIndex < 0) return null
65
+ const pathFromJunction = simplifyPath([
66
+ branchPoint,
67
+ ...pathFromSharedPin.slice(branchSegmentIndex + 1),
68
+ ])
69
+ let tracePath = pathFromJunction
70
+ if (trace.pins[0]?.pinId !== sharedPinId) {
71
+ tracePath = pathFromJunction.reverse()
72
+ }
73
+ return { ...trace, tracePath }
74
+ }
75
+
76
+ export const collapseRedundantSharedEndpointStubs = ({
77
+ traces,
78
+ netLabelPlacements,
79
+ netLabelConnectorTraceIds,
80
+ multiPinNetPinIds,
81
+ }: {
82
+ traces: SolvedTracePath[]
83
+ netLabelPlacements: NetLabelPlacement[]
84
+ netLabelConnectorTraceIds: ReadonlySet<MspConnectionPairId>
85
+ multiPinNetPinIds: ReadonlySet<PinId>
86
+ }) => {
87
+ const outputTraces = [...traces]
88
+
89
+ for (let firstIndex = 0; firstIndex < outputTraces.length; firstIndex++) {
90
+ const firstTrace = outputTraces[firstIndex]!
91
+ if (netLabelConnectorTraceIds.has(firstTrace.mspPairId)) continue
92
+ for (
93
+ let secondIndex = firstIndex + 1;
94
+ secondIndex < outputTraces.length;
95
+ secondIndex++
96
+ ) {
97
+ const secondTrace = outputTraces[secondIndex]!
98
+ if (
99
+ netLabelConnectorTraceIds.has(secondTrace.mspPairId) ||
100
+ firstTrace.globalConnNetId !== secondTrace.globalConnNetId
101
+ ) {
102
+ continue
103
+ }
104
+ const sharedPin = getSharedPin({
105
+ donorTrace: firstTrace,
106
+ branchTrace: secondTrace,
107
+ })
108
+ if (!sharedPin || !multiPinNetPinIds.has(sharedPin.pinId)) continue
109
+ const firstPath = getPathFromSharedPin({
110
+ trace: firstTrace,
111
+ sharedPinId: sharedPin.pinId,
112
+ })
113
+ const secondPath = getPathFromSharedPin({
114
+ trace: secondTrace,
115
+ sharedPinId: sharedPin.pinId,
116
+ })
117
+ if (firstPath.length < 2 || secondPath.length < 2) continue
118
+
119
+ const departureAxis = getSegmentAxis(firstPath[0]!, firstPath[1]!)
120
+ const secondDepartureAxis = getSegmentAxis(secondPath[0]!, secondPath[1]!)
121
+ if (!departureAxis || departureAxis !== secondDepartureAxis) continue
122
+
123
+ const firstDepartureDirection =
124
+ firstPath[1]![departureAxis] - firstPath[0]![departureAxis]
125
+ const secondDepartureDirection =
126
+ secondPath[1]![departureAxis] - secondPath[0]![departureAxis]
127
+ if (
128
+ !pointsEqual(firstPath[1]!, secondPath[1]!) &&
129
+ Math.sign(firstDepartureDirection) ===
130
+ Math.sign(secondDepartureDirection)
131
+ ) {
132
+ let traceIndexToCollapse = secondIndex
133
+ let traceToCollapse = secondTrace
134
+ let pathToCollapse = secondPath
135
+ if (
136
+ Math.abs(firstDepartureDirection) < Math.abs(secondDepartureDirection)
137
+ ) {
138
+ traceIndexToCollapse = firstIndex
139
+ traceToCollapse = firstTrace
140
+ pathToCollapse = firstPath
141
+ }
142
+ const branchPoint = pathToCollapse[1]!
143
+ if (
144
+ !traceCanLoseSharedPrefix({
145
+ trace: traceToCollapse,
146
+ sharedPrefix: [pathToCollapse[0]!, branchPoint],
147
+ netLabelPlacements,
148
+ })
149
+ ) {
150
+ if (traceIndexToCollapse === firstIndex) {
151
+ traceIndexToCollapse = secondIndex
152
+ traceToCollapse = secondTrace
153
+ pathToCollapse = secondPath
154
+ } else {
155
+ traceIndexToCollapse = firstIndex
156
+ traceToCollapse = firstTrace
157
+ pathToCollapse = firstPath
158
+ }
159
+ }
160
+ if (
161
+ !traceCanLoseSharedPrefix({
162
+ trace: traceToCollapse,
163
+ sharedPrefix: [pathToCollapse[0]!, branchPoint],
164
+ netLabelPlacements,
165
+ })
166
+ ) {
167
+ continue
168
+ }
169
+ const collapsedTrace = getTraceWithoutSharedPrefix({
170
+ trace: traceToCollapse,
171
+ pathFromSharedPin: pathToCollapse,
172
+ sharedPinId: sharedPin.pinId,
173
+ branchPoint,
174
+ })
175
+ if (!collapsedTrace) continue
176
+ outputTraces[traceIndexToCollapse] = collapsedTrace
177
+ break
178
+ }
179
+ if (firstPath.length < 3 || secondPath.length < 3) continue
180
+ if (!pointsEqual(firstPath[1]!, secondPath[1]!)) continue
181
+
182
+ const railAxis = getSegmentAxis(firstPath[1]!, firstPath[2]!)
183
+ const secondRailAxis = getSegmentAxis(secondPath[1]!, secondPath[2]!)
184
+ if (
185
+ !railAxis ||
186
+ railAxis !== secondRailAxis ||
187
+ railAxis === departureAxis
188
+ ) {
189
+ continue
190
+ }
191
+ const firstRailDirection =
192
+ firstPath[2]![railAxis] - firstPath[1]![railAxis]
193
+ const secondRailDirection =
194
+ secondPath[2]![railAxis] - secondPath[1]![railAxis]
195
+ const railsHaveSameDirection =
196
+ Math.sign(firstRailDirection) === Math.sign(secondRailDirection)
197
+ const sharedStub = firstPath.slice(0, 2)
198
+ if (
199
+ !railsHaveSameDirection &&
200
+ !nearlyEqual(
201
+ Math.abs(firstRailDirection),
202
+ Math.abs(secondRailDirection),
203
+ ) &&
204
+ netLabelPlacements.some(
205
+ (label) =>
206
+ label.globalConnNetId === firstTrace.globalConnNetId &&
207
+ tracePathContainsPoint(sharedStub, label.anchorPoint),
208
+ )
209
+ ) {
210
+ continue
211
+ }
212
+ let traceIndexToCollapse = secondIndex
213
+ let traceToCollapse = secondTrace
214
+ let pathToCollapse = secondPath
215
+ let branchPoint = firstPath[1]!
216
+ if (
217
+ railsHaveSameDirection &&
218
+ Math.abs(firstRailDirection) < Math.abs(secondRailDirection)
219
+ ) {
220
+ traceIndexToCollapse = firstIndex
221
+ traceToCollapse = firstTrace
222
+ pathToCollapse = firstPath
223
+ branchPoint = firstPath[2]!
224
+ } else if (railsHaveSameDirection) {
225
+ branchPoint = secondPath[2]!
226
+ }
227
+ const sharedPrefix = [pathToCollapse[0]!, pathToCollapse[1]!, branchPoint]
228
+ if (
229
+ !traceCanLoseSharedPrefix({
230
+ trace: traceToCollapse,
231
+ sharedPrefix,
232
+ netLabelPlacements,
233
+ })
234
+ ) {
235
+ if (traceIndexToCollapse === firstIndex) {
236
+ traceIndexToCollapse = secondIndex
237
+ traceToCollapse = secondTrace
238
+ pathToCollapse = secondPath
239
+ } else {
240
+ traceIndexToCollapse = firstIndex
241
+ traceToCollapse = firstTrace
242
+ pathToCollapse = firstPath
243
+ }
244
+ }
245
+ if (
246
+ !traceCanLoseSharedPrefix({
247
+ trace: traceToCollapse,
248
+ sharedPrefix: [pathToCollapse[0]!, pathToCollapse[1]!, branchPoint],
249
+ netLabelPlacements,
250
+ })
251
+ ) {
252
+ continue
253
+ }
254
+ const collapsedTrace = getTraceWithoutSharedPrefix({
255
+ trace: traceToCollapse,
256
+ pathFromSharedPin: pathToCollapse,
257
+ sharedPinId: sharedPin.pinId,
258
+ branchPoint,
259
+ })
260
+ if (!collapsedTrace) continue
261
+ outputTraces[traceIndexToCollapse] = collapsedTrace
262
+ break
263
+ }
264
+ }
265
+
266
+ return outputTraces
267
+ }
@@ -11,7 +11,7 @@ import {
11
11
  pointsEqual,
12
12
  } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
13
13
  import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
14
- import { getSharedPin } from "./alignSameNetJunctions"
14
+ import { getSharedPin } from "./getSharedPin"
15
15
  import {
16
16
  pathEntersAnyNetLabel,
17
17
  pathIntersectsAnyNetLabel,